Scroll Modes

The native Vue Grid by Kendo UI enables you to configure its layout modes.

You can set the following modes to the Grid:

Scrollable

When scrolling is enabled, the content of the Grid is rendered as tables—one for the header area, another one for the scrollable data area, and a third one for the footer area. This behavior ensures that the header and footer areas of the Grid are always visible while the user scrolls vertically.
The scrollable mode is enabled by default. To configure it, use the scrollable option, which also requires you to set the height of the Grid through its style property.

Conditional Scrolling

By configuring the style.maxHeight property, you can set the Grid in scrollable mode only when its rendered content exceeds certain height limits. If the content does not exceed the set maximum height, the height of the Grid will be the same as the height of its content.

Non-Scrollable

When the non-scrollable mode is enabled, the Grid renders its data as a single table and the scrollbar is not displayed. To configure the non-scrollable mode, set scrollable to none.

Virtual Scrolling

Virtual scrolling provides an alternative to paging. While the user is scrolling the table, the Grid requests and displays only the visible pages.

Setup

To set up the Grid for virtual scrolling:

  1. Set its height either through its style property.
  2. Set the regular or detail row height and the number of records.
  3. Provide the data for each page through the onPageChange event handler.

To work properly, virtual scrolling requires you to set the following configuration options:

  • (Required) scrollable—Set it to virtual.
  • (Required) height through style
  • (Required) skip
  • (Required) total
  • (Required) pageSize—To avoid unexpected behavior during scrolling, set pageSize to at least the number of the visible Grid elements. The number of the visible Grid elements is determined by the height and rowHeight settings of the Grid.
  • (Required) data
  • (Optional) rowHeight
<div id="vueapp" class="vue-app">
    <Grid style="height: 440px;"
            :data-items="items"
            :columns="columns"
            :total="numberOfItems"
            :row-height="40"
            :skip="skip"
            :page-size="20"
            :scrollable="'virtual'"
            @pagechange="pageChange">
    </Grid>
</div>
import { Grid } from '@progress/kendo-vue-grid';

Vue.component('Grid', Grid);

new Vue({
    el: '#vueapp',
    data: function () {
        return {
            dataItems: [],
            items: [],
            numberOfItems: 50000,
            columns: [
                { field: 'id', title: 'ID', width: '70px'},
                { field: 'firstName', title: 'First Name'},
                { field: 'lastName', title: 'Last Name'},
                { field: 'city', title: 'City', width: '120px'},
                { field: 'title', title: 'Title', width: '200px'}
            ],
            skip: 0
        };
    },
    created: function() {
      this.dataItems = this.createRandomData(this.numberOfItems);
      this.items = this.dataItems.slice(this.skip, this.skip + 20);
    },
    methods: {
        pageChange(event) {
            this.skip = event.page.skip;
            this.items = this.dataItems.slice(this.skip, this.skip + 20);
        },

        /* Generating example data */
        createRandomData(count) {
            const firstNames = [ 'Nancy', 'Andrew', 'Janet', 'Margaret', 'Steven',
             'Michael', 'Robert', 'Laura', 'Anne', 'Nige' ],
            lastNames = [ 'Davolio', 'Fuller', 'Leverling', 'Peacock',
                'Buchanan', 'Suyama', 'King', 'Callahan', 'Dodsworth', 'White' ],
            cities = [ 'Seattle', 'Tacoma', 'Kirkland', 'Redmond',
                'London', 'Philadelphia', 'New York', 'Seattle', 'London', 'Boston' ],
            titles = [ 'Accountant', 'Vice President, Sales', 'Sales Representative',
            'Technical Support', 'Sales Manager', 'Web Designer',
                'Software Developer' ];

            return Array(count).fill({}).map((_, idx) => ({
                id: idx + 1,
                firstName: firstNames[Math.floor(Math.random() * firstNames.length)],
                lastName: lastNames[Math.floor(Math.random() * lastNames.length)],
                city: cities[Math.floor(Math.random() * cities.length)],
                title: titles[Math.floor(Math.random() * titles.length)]
            }));
        }
    }
});

Debouncing pageChange Events

When configured for virtualization, the Grid fires the onPageChange event as often as possible. This behavior allows for a smoother scrolling experience when the data is available in memory.

If the data is requested from a remote service, debounce or otherwise limit the page changes.

<div id="vueapp" class="vue-app">
    <Grid style="height: 440px;"
          :data-items="items"
          :columns="columns"
          :total="total"
          :row-height="40"
          :skip="skip"
          :row-height="40"
          :scrollable="'virtual'"
          @pagechange="pageChange">
    </Grid>
</div>
import { Grid } from '@progress/kendo-vue-grid';

Vue.component('Grid', Grid);

new Vue({
    el: '#vueapp',
    mounted() {
        // request the first page on initial load
        this.requestData(0);
    },
    data: function () {
        return {
            tempSkip: 0,
            requestInProgress: false,
            pageSize: 20,
            baseUrl: 'https://demos.telerik.com/kendo-ui/service-v4/odata/Orders?$count=true&$top=60&$skip=',
            init: { method: 'GET', accept: 'application/json', headers: {} },
            orders: [],
            items: [],
            total: 0,
            skip: 0,
            columns: [
                { field: 'Index', title: 'Index', width: '70px'},
                { field: 'OrderID', title: 'Order Id', width: '100px'},
                { field: 'ShipCountry', title: 'Ship Country'},
                { field: 'ShipName', title: 'Ship Name'}
            ]
        };
    },
    methods: {
        pageChange(event) {
            this.requestIfNeeded(event.page.skip);
            this.skip = event.page.skip;
            this.items = this.orders.slice(this.skip, this.skip + 20);
        },

        requestIfNeeded(skip) {
            for (let i = skip; i < skip + this.pageSize && i < this.orders.length; i++) {
                if (this.orders[i].OrderID === undefined) {

                    this.requestData(skip);
                    return;
                }
            }
        },

        requestData(skipParameter) {
            if (this.requestInProgress) {

                return;
            }
            this.requestInProgress = true;
            var that = this;
            const skip = Math.max(skipParameter - this.pageSize, 0);

            fetch(this.baseUrl + skip, this.init)
                .then(response => response.json())
                .then(json => {
                    that.requestInProgress = false;

                    const total = json['@odata.count'];
                    const data = json['value'];
                    const orders = new Array(total).fill().map((e, i) => ({ Index: i }));

                    data.forEach((order, i) => {
                        orders[i + skip] = { Index: i + skip, ...order };
                    });

                    that.requestIfNeeded(that.skip);
                    that.orders = orders;
                    that.items = that.orders.slice(that.skip, that.skip + 20);
                    that.total = total;
                });
        }
    }
});

In this article