Column Menu

The Native Vue Grid enables you to show a menu with quick actions for its columns.

The column menu provides flexible options for high-level customization. For example, the regular sorting and filtering features are represented by individual components which allows you to implement complex scenarios and meet the specific requirements of your project.

Basic Usage

To configure the column menu, use the columnMenu option of the columns.

The following example demonstrates how to utilize the components for sorting and filtering while you implement the column menu in the Grid.

<div id="vueapp" class="vue-app">
   <Grid
        :data-items="gridData"
        :take="take"
        :skip="skip"
        :sortable="true"
        :sort= "sort"
        :filter="filter"
        @dataStateChange="dataStateChange"
        :pageable="true"
        :column-menu="columnMenu"
        :columns="columns"
    >
    </Grid>
</div>
import {
    Grid, GridColumnMenuSort,
    GridColumnMenuFilter
} from '@progress/kendo-vue-grid';
import { process } from '@progress/kendo-data-query';

var products = [
    {
        "ProductID": 1,
        "ProductName": "Chai",
        "SupplierID": 1,
        "CategoryID": 1,
        "QuantityPerUnit": "10 boxes x 20 bags",
        "UnitPrice": 18.0000,
        "UnitsInStock": 39,
        "UnitsOnOrder": 0,
        "ReorderLevel": 10,
        "Discontinued": false,
        "Category": {
            "CategoryID": 1,
            "CategoryName": "Beverages",
            "Description": "Soft drinks, coffees, teas, beers, and ales"
        }
    },
    {
        "ProductID": 2,
        "ProductName": "Chang",
        "SupplierID": 1,
        "CategoryID": 1,
        "QuantityPerUnit": "24 - 12 oz bottles",
        "UnitPrice": 19.0000,
        "UnitsInStock": 17,
        "UnitsOnOrder": 40,
        "ReorderLevel": 25,
        "Discontinued": false,
        "Category": {
            "CategoryID": 1,
            "CategoryName": "Beverages",
            "Description": "Soft drinks, coffees, teas, beers, and ales"
        }
    },
    {
        "ProductID": 3,
        "ProductName": "Aniseed Syrup",
        "SupplierID": 1,
        "CategoryID": 2,
        "QuantityPerUnit": "12 - 550 ml bottles",
        "UnitPrice": 10.0000,
        "UnitsInStock": 13,
        "UnitsOnOrder": 70,
        "ReorderLevel": 25,
        "Discontinued": false,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        }
    },
    {
        "ProductID": 4,
        "ProductName": "Chef Anton's Cajun Seasoning",
        "SupplierID": 2,
        "CategoryID": 2,
        "QuantityPerUnit": "48 - 6 oz jars",
        "UnitPrice": 22.0000,
        "UnitsInStock": 53,
        "UnitsOnOrder": 0,
        "ReorderLevel": 0,
        "Discontinued": false,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        }
    },
    {
        "ProductID": 5,
        "ProductName": "Chef Anton's Gumbo Mix",
        "SupplierID": 2,
        "CategoryID": 2,
        "QuantityPerUnit": "36 boxes",
        "UnitPrice": 21.3500,
        "UnitsInStock": 0,
        "UnitsOnOrder": 0,
        "ReorderLevel": 0,
        "Discontinued": true,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        }
    },
    {
        "ProductID": 6,
        "ProductName": "Grandma's Boysenberry Spread",
        "SupplierID": 3,
        "CategoryID": 2,
        "QuantityPerUnit": "12 - 8 oz jars",
        "UnitPrice": 25.0000,
        "UnitsInStock": 120,
        "UnitsOnOrder": 0,
        "ReorderLevel": 25,
        "Discontinued": false,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        }
    },
    {
        "ProductID": 7,
        "ProductName": "Uncle Bob's Organic Dried Pears",
        "SupplierID": 3,
        "CategoryID": 7,
        "QuantityPerUnit": "12 - 1 lb pkgs.",
        "UnitPrice": 30.0000,
        "UnitsInStock": 15,
        "UnitsOnOrder": 0,
        "ReorderLevel": 10,
        "Discontinued": false,
        "Category": {
            "CategoryID": 7,
            "CategoryName": "Produce",
            "Description": "Dried fruit and bean curd"
        }
    },
    {
        "ProductID": 8,
        "ProductName": "Northwoods Cranberry Sauce",
        "SupplierID": 3,
        "CategoryID": 2,
        "QuantityPerUnit": "12 - 12 oz jars",
        "UnitPrice": 40.0000,
        "UnitsInStock": 6,
        "UnitsOnOrder": 0,
        "ReorderLevel": 0,
        "Discontinued": false,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        }
    }];
Vue.component('Grid', Grid);

new Vue({
    el: '#vueapp',
    data: function () {
        return {
            columnMenu: true,
            columns: [
                { field: 'ProductID', title: 'ID',
                filter: 'numeric'},
                { field: 'ProductName', title: 'Name'},
                { field: 'UnitPrice', filter: 'numeric'},
                { field: 'Discontinued', filter: 'boolean'}
            ],
            take: 10,
            skip: 0,
            gridData: [],
            sort: [],
            filter: null
        };
    },
   created: function() {
        this.getData();
    },
    methods: {
        getData: function () {
            let dataState = {
                take: this.take,
                skip: this.skip,
                filter: this.filter,
                sort: this.sort
            };
            this.gridData = process(products, dataState);
        },
        createAppState: function(dataState) {
            this.take = dataState.take;
            this.skip = dataState.skip;
            this.sort = dataState.sort;
            this.filter = dataState.filter;
            this.getData();
        },
        dataStateChange: function (event) {
            this.createAppState(event.data);
        },
        expandChange: function (event) {
            Vue.set(event.dataItem, event.target.$props.expandField, event.value);
        }
    }
});

Customizing the Filter Component

The filter component of the column menu enables you to customize its user interface (UI) by passing a custom component to the filterUI property.

The following example demonstrates how to customize the UI of the column-menu filter component.

<div id="vueapp" class="vue-app">
   <Grid
        :data-items="gridData"
        :take="take"
        :skip="skip"
        :sortable="true"
        :sort= "sort"
        :filter="filter"
        @dataStateChange="dataStateChange"
        :pageable="true"
        :columns="columns"
    >
    </Grid>
</div>
import {
    Grid, GridColumnMenuSort,
    GridColumnMenuFilter
} from '@progress/kendo-vue-grid';
import { process } from '@progress/kendo-data-query';
var products = [
    {
        "ProductID": 1,
        "ProductName": "Chai",
        "SupplierID": 1,
        "CategoryID": 1,
        "QuantityPerUnit": "10 boxes x 20 bags",
        "UnitPrice": 18.0000,
        "UnitsInStock": 39,
        "UnitsOnOrder": 0,
        "ReorderLevel": 10,
        "Discontinued": false,
        "Category": {
            "CategoryID": 1,
            "CategoryName": "Beverages",
            "Description": "Soft drinks, coffees, teas, beers, and ales"
        }
    },
    {
        "ProductID": 2,
        "ProductName": "Chang",
        "SupplierID": 1,
        "CategoryID": 1,
        "QuantityPerUnit": "24 - 12 oz bottles",
        "UnitPrice": 19.0000,
        "UnitsInStock": 17,
        "UnitsOnOrder": 40,
        "ReorderLevel": 25,
        "Discontinued": false,
        "Category": {
            "CategoryID": 1,
            "CategoryName": "Beverages",
            "Description": "Soft drinks, coffees, teas, beers, and ales"
        }
    },
    {
        "ProductID": 3,
        "ProductName": "Aniseed Syrup",
        "SupplierID": 1,
        "CategoryID": 2,
        "QuantityPerUnit": "12 - 550 ml bottles",
        "UnitPrice": 10.0000,
        "UnitsInStock": 13,
        "UnitsOnOrder": 70,
        "ReorderLevel": 25,
        "Discontinued": false,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        }
    },
    {
        "ProductID": 4,
        "ProductName": "Chef Anton's Cajun Seasoning",
        "SupplierID": 2,
        "CategoryID": 2,
        "QuantityPerUnit": "48 - 6 oz jars",
        "UnitPrice": 22.0000,
        "UnitsInStock": 53,
        "UnitsOnOrder": 0,
        "ReorderLevel": 0,
        "Discontinued": false,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        }
    },
    {
        "ProductID": 5,
        "ProductName": "Chef Anton's Gumbo Mix",
        "SupplierID": 2,
        "CategoryID": 2,
        "QuantityPerUnit": "36 boxes",
        "UnitPrice": 21.3500,
        "UnitsInStock": 0,
        "UnitsOnOrder": 0,
        "ReorderLevel": 0,
        "Discontinued": true,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        }
    },
    {
        "ProductID": 6,
        "ProductName": "Grandma's Boysenberry Spread",
        "SupplierID": 3,
        "CategoryID": 2,
        "QuantityPerUnit": "12 - 8 oz jars",
        "UnitPrice": 25.0000,
        "UnitsInStock": 120,
        "UnitsOnOrder": 0,
        "ReorderLevel": 25,
        "Discontinued": false,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        }
    },
    {
        "ProductID": 7,
        "ProductName": "Uncle Bob's Organic Dried Pears",
        "SupplierID": 3,
        "CategoryID": 7,
        "QuantityPerUnit": "12 - 1 lb pkgs.",
        "UnitPrice": 30.0000,
        "UnitsInStock": 15,
        "UnitsOnOrder": 0,
        "ReorderLevel": 10,
        "Discontinued": false,
        "Category": {
            "CategoryID": 7,
            "CategoryName": "Produce",
            "Description": "Dried fruit and bean curd"
        }
    },
    {
        "ProductID": 8,
        "ProductName": "Northwoods Cranberry Sauce",
        "SupplierID": 3,
        "CategoryID": 2,
        "QuantityPerUnit": "12 - 12 oz jars",
        "UnitPrice": 40.0000,
        "UnitsInStock": 6,
        "UnitsOnOrder": 0,
        "ReorderLevel": 0,
        "Discontinued": false,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        }
    }];

const ColumnMenu = Vue.component('columnmenu-component', {
    props: {
        column: Object,
        sortable: [Boolean, Object],
        sort: {
            type: Array
        },
        filter: Object,
        filterable: Boolean
    },
    template: `<div>
        <GridColumnMenuFilter
            :column="column"
            :filterable="filterable"
            :filter="filter"
            @closemenu ="closeMenu"
            @expandchange = "expandChange"
            @filterchange = "filterChange"
        />
    </div>`,
    methods: {
        expandChange () {
            this.$emit('expandchange');
        },
        closeMenu () {
            this.$emit('closemenu');
        },
        filterChange (newDescriptor, e) {
            this.$emit('filterchange', newDescriptor, e);
        }
    }
});

Vue.component('GridColumnMenuFilter', GridColumnMenuFilter);
Vue.component('Grid', Grid);

new Vue({
    el: '#vueapp',
    data: function () {
        return {
            columns: [
                    { field: 'ProductName', title: 'Name'},
                    { field: 'Discontinued', filter: 'boolean',
                    columnMenu: ColumnMenu}
            ],
            take: 10,
            skip: 0,
            gridData: [],
            sort: [],
            filter: null
        };
    },
   created: function() {
        this.getData();
    },
    methods: {
        getData: function () {
            let dataState = {
                take: this.take,
                skip: this.skip,
                filter: this.filter,
                sort: this.sort
            };
            this.gridData = process(products, dataState);
        },
        createAppState: function(dataState) {
            this.take = dataState.take;
            this.skip = dataState.skip;
            this.sort = dataState.sort;
            this.filter = dataState.filter;
            this.getData();
        },
        dataStateChange: function (event) {
            this.createAppState(event.data);
        },
        expandChange: function (event) {
            Vue.set(event.dataItem, event.target.$props.expandField, event.value);
        }
    }
});

In the above example the filter column menu could also be defined as a local component instance in the following way:

const ColumnMenu = {
    props: {
        column: Object,
        sortable: [Boolean, Object],
        sort: {
            type: Array
        },
        filter: Object,
        filterable: Boolean
    },
    template: `<div>
        <GridColumnMenuFilter
            :column="column"
            :filterable="filterable"
            :filter="filter"
            @closemenu ="closeMenu"
            @expandchange = "expandChange"
            @filterchange = "filterChange"
        />
    </div>`,
    methods: {
        expandChange () {
            this.$emit('expandchange');
        },
        closeMenu () {
            this.$emit('closemenu');
        },
        filterChange (newDescriptor, e) {
            this.$emit('filterchange', newDescriptor, e);
        }
    }
};

Styling the Column Menu Icon

Both the filterGroupByField and sortGroupByField static methods which checks if filtering and sorting are applied to a specific field. You can use these methods for applying custom CSS classes to the column menu and mark it as active.

The following example demonstrates how to style the column-menu icon when sorting and filtering are applied.

<style>
    th.k-header.active > div > div {
        color: #fff;
        background-color: #ff6358;
    }
</style>

<div id="vueapp" class="vue-app">
   <Grid
        :data-items="gridData"
        :take="take"
        :skip="skip"
        :sortable="true"
        :sort= "sort"
        :filter="filter"
        @dataStateChange="dataStateChange"
        @filterchange="filterChange"
        @sortchange="sortChange"
        :pageable="true"
        :column-menu="true"
        :columns="columns"
    >
    </Grid>
</div>
import {
    Grid, GridColumnMenuSort,
    GridColumnMenuFilter, filterGroupByField, sortGroupByField
} from '@progress/kendo-vue-grid';
import { process } from '@progress/kendo-data-query';
var products = [
    {
        "ProductID": 1,
        "ProductName": "Chai",
        "SupplierID": 1,
        "CategoryID": 1,
        "QuantityPerUnit": "10 boxes x 20 bags",
        "UnitPrice": 18.0000,
        "UnitsInStock": 39,
        "UnitsOnOrder": 0,
        "ReorderLevel": 10,
        "Discontinued": false,
        "Category": {
            "CategoryID": 1,
            "CategoryName": "Beverages",
            "Description": "Soft drinks, coffees, teas, beers, and ales"
        }
    },
    {
        "ProductID": 2,
        "ProductName": "Chang",
        "SupplierID": 1,
        "CategoryID": 1,
        "QuantityPerUnit": "24 - 12 oz bottles",
        "UnitPrice": 19.0000,
        "UnitsInStock": 17,
        "UnitsOnOrder": 40,
        "ReorderLevel": 25,
        "Discontinued": false,
        "Category": {
            "CategoryID": 1,
            "CategoryName": "Beverages",
            "Description": "Soft drinks, coffees, teas, beers, and ales"
        }
    },
    {
        "ProductID": 3,
        "ProductName": "Aniseed Syrup",
        "SupplierID": 1,
        "CategoryID": 2,
        "QuantityPerUnit": "12 - 550 ml bottles",
        "UnitPrice": 10.0000,
        "UnitsInStock": 13,
        "UnitsOnOrder": 70,
        "ReorderLevel": 25,
        "Discontinued": false,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        }
    },
    {
        "ProductID": 4,
        "ProductName": "Chef Anton's Cajun Seasoning",
        "SupplierID": 2,
        "CategoryID": 2,
        "QuantityPerUnit": "48 - 6 oz jars",
        "UnitPrice": 22.0000,
        "UnitsInStock": 53,
        "UnitsOnOrder": 0,
        "ReorderLevel": 0,
        "Discontinued": false,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        }
    },
    {
        "ProductID": 5,
        "ProductName": "Chef Anton's Gumbo Mix",
        "SupplierID": 2,
        "CategoryID": 2,
        "QuantityPerUnit": "36 boxes",
        "UnitPrice": 21.3500,
        "UnitsInStock": 0,
        "UnitsOnOrder": 0,
        "ReorderLevel": 0,
        "Discontinued": true,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        }
    },
    {
        "ProductID": 6,
        "ProductName": "Grandma's Boysenberry Spread",
        "SupplierID": 3,
        "CategoryID": 2,
        "QuantityPerUnit": "12 - 8 oz jars",
        "UnitPrice": 25.0000,
        "UnitsInStock": 120,
        "UnitsOnOrder": 0,
        "ReorderLevel": 25,
        "Discontinued": false,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        }
    },
    {
        "ProductID": 7,
        "ProductName": "Uncle Bob's Organic Dried Pears",
        "SupplierID": 3,
        "CategoryID": 7,
        "QuantityPerUnit": "12 - 1 lb pkgs.",
        "UnitPrice": 30.0000,
        "UnitsInStock": 15,
        "UnitsOnOrder": 0,
        "ReorderLevel": 10,
        "Discontinued": false,
        "Category": {
            "CategoryID": 7,
            "CategoryName": "Produce",
            "Description": "Dried fruit and bean curd"
        }
    },
    {
        "ProductID": 8,
        "ProductName": "Northwoods Cranberry Sauce",
        "SupplierID": 3,
        "CategoryID": 2,
        "QuantityPerUnit": "12 - 12 oz jars",
        "UnitPrice": 40.0000,
        "UnitsInStock": 6,
        "UnitsOnOrder": 0,
        "ReorderLevel": 0,
        "Discontinued": false,
        "Category": {
            "CategoryID": 2,
            "CategoryName": "Condiments",
            "Description": "Sweet and savory sauces, relishes, spreads, and seasonings"
        }
    }];




Vue.component('GridColumnMenuFilter', GridColumnMenuFilter);
Vue.component('Grid', Grid);

new Vue({
    el: '#vueapp',
    data: function () {
        return {
            columnMenu: true,
            columns: [
                    { field: 'ProductName', title: 'Name'},
                    { field: 'Discontinued', filter: 'boolean'}
            ],
            take: 10,
            skip: 0,
            gridData: [],
            sort: [],
            filter: null
        };
    },
   created: function() {
        this.getData();
    },
    methods: {
        getData: function () {
            let dataState = {
                take: this.take,
                skip: this.skip,
                filter: this.filter,
                sort: this.sort
            };
            this.gridData = process(products, dataState);
        },
        createAppState: function(dataState) {
            this.take = dataState.take;
            this.skip = dataState.skip;
            this.sort = dataState.sort;
            this.filter = dataState.filter;
            this.getData();
        },
        dataStateChange: function (event) {
            this.createAppState(event.data);
        },
        filterChange:  function (event) {
            let isColumnActive = filterGroupByField(event.event.field, event.filter);
            // @ts-ignore
            let changedColumn = this.columns.find(function(column) {
                return column.field === event.event.field;
            });

            if (changedColumn) {
                changedColumn.headerClassName = isColumnActive ? 'active' : '';
            }

            this.filter = event.filter;
            this.getData();
        },
        sortChange:  function (event) {
            let isColumnActive = sortGroupByField(event.event.field, event.sort);
            // @ts-ignore
            this.columns.find(function(column) {
                return column.field === event.event.field;
              }).headerClassName = isColumnActive ? 'active' : '';

            this.sort = event.sort;
            this.getData();
        },
        expandChange: function (event) {
            Vue.set(event.dataItem, event.target.$props.expandField, event.value);
        }
    }
});

In this article