Row Context Menu
This example demonstrates how to create a custom row context menu with interactive actions. The menu appears when right-clicking on a row header and provides menu items with actions.
Key * Features:
- Custom row context menu implementation
- Actions with visual indicators
- Styled menu buttons with hover effects
Source code
import '@fortawesome/fontawesome-free/css/all.min.css';
import { defineCustomElements } from '@revolist/revogrid/loader';defineCustomElements();
import { currentTheme, useRandomData } from '../composables/useRandomData';import { ContextMenuPlugin, RowHeaderPlugin, ColumnStretchPlugin, RowOddPlugin } from '@revolist/revogrid-pro';import { createContextMenuConfig } from './context-menu.config';const { createRandomData } = useRandomData();const { isDark } = currentTheme();
const grid = document.querySelector('revo-grid');if (grid) { grid.source = createRandomData(100); // Define columns grid.columns = [ { name: '#', prop: 'id', size: 70, }, { name: '🍎 Fruit', prop: 'name', }, { name: '💰 Price', prop: 'price', }, ]; // Define plugin grid.plugins = [RowHeaderPlugin, ContextMenuPlugin, ColumnStretchPlugin, RowOddPlugin]; // grid.rowHeaders = rowHeaders({ showHeaderFocusBtn: true });
grid.additionalData = { // Define context menu rowContextMenu: createContextMenuConfig, stretch: 'all' };
// Set theme grid.theme = isDark() ? 'darkMaterial' : 'material';}<template> <RevoGrid class="rounded-lg overflow-hidden" :theme="isDark ? 'darkMaterial' : 'material'" :source="source" :columns="columns" :plugins="plugins" :additionalData="additionalData" :row-headers="rowHeadersConfig" hide-attribution style="min-height: 300px;" /></template>
<script setup lang="ts">import RevoGrid, { type ColumnRegular } from '@revolist/vue3-datagrid';import { ContextMenuPlugin, RowHeaderPlugin, ColumnStretchPlugin, RowOddPlugin, RowOrderPlugin, rowHeaders } from '@revolist/revogrid-pro';import { ref, computed } from 'vue';import { createContextMenuConfig } from './context-menu.config';import { makeData } from '../composables/makeData';import { currentThemeVue } from '../composables/useRandomData';
const { isDark } = currentThemeVue();
const source = ref(makeData(100));
const columns: ColumnRegular[] = [ { name: '#', prop: 'id', size: 150, }, { name: 'Name', prop: 'fullName', }, { name: 'Job Title', prop: 'jobTitle', },];
const plugins = [RowHeaderPlugin, ContextMenuPlugin, ColumnStretchPlugin, RowOddPlugin, RowOrderPlugin];
const additionalData = computed(() => ({ // Define context menu rowContextMenu: createContextMenuConfig, stretch: 'all'}));const rowHeadersConfig = ref( rowHeaders({ showHeaderFocusBtn: false, rowDrag: true }),);</script>
<style lang="css" src="@fortawesome/fontawesome-free/css/all.min.css"/>
<style scoped>:deep(.rowHeaders) { revogr-data .rgCell { padding: 0 !important; }}
:deep(.row-header-holder) { button { width: 100%; border: 0; background: none;
&:hover { background-color: var(--sl-color-gray-6); } }}</style>import '@fortawesome/fontawesome-free/css/all.min.css';
import React, { useMemo, useRef } from 'react';import { RevoGrid, type ColumnRegular } from '@revolist/react-datagrid';import { ContextMenuPlugin, RowHeaderPlugin, ColumnStretchPlugin, RowOddPlugin } from '@revolist/revogrid-pro';import { createContextMenuConfig } from './context-menu.config';import { makeData } from '../composables/makeData';import { currentTheme } from '../composables/useRandomData';
const { isDark } = currentTheme();
function RowContext() { const source = useMemo(() => makeData(100), []);
const columns: ColumnRegular[] = useMemo( () => [ { name: '#', prop: 'id', size: 70, }, { name: '🍎 Fruit', prop: 'name', }, { name: '💰 Price', prop: 'price', }, ], [], );
const plugins = [RowHeaderPlugin, ContextMenuPlugin, ColumnStretchPlugin, RowOddPlugin] as any;
const additionalData = useMemo(() => ({ // Define context menu rowContextMenu: createContextMenuConfig, stretch: 'all' }), []);
const RevoGridComponent = RevoGrid as any;
return ( <RevoGridComponent theme={isDark() ? 'darkMaterial' : 'material'} source={source} columns={columns} plugins={plugins} additionalData={additionalData} hideAttribution style={{ minHeight: '300px' }} /> );}
export default RowContext;import '@fortawesome/fontawesome-free/css/all.min.css';
import { Component, ViewEncapsulation, OnInit } from '@angular/core';import { RevoGrid } from '@revolist/angular-datagrid';import { ContextMenuPlugin, RowHeaderPlugin, ColumnStretchPlugin, RowOddPlugin } from '@revolist/revogrid-pro';import { createContextMenuConfig } from './context-menu.config';import { makeData } from '../composables/makeData';import { currentTheme } from '../composables/useRandomData';
@Component({ selector: 'row-context-grid', standalone: true, imports: [RevoGrid], template: ` <revo-grid [source]="source" [columns]="columns" [plugins]="plugins" [theme]="theme" [additionalData]="additionalData" [hideAttribution]="true" range style="min-height: 300px;" ></revo-grid> `, encapsulation: ViewEncapsulation.None,})export class RowContextGridComponent implements OnInit { source = makeData(100);
columns = [ { name: '#', prop: 'id', size: 150, }, { name: 'Name', prop: 'fullName', }, { name: 'Job Title', prop: 'jobTitle', }, ];
plugins = [RowHeaderPlugin, ContextMenuPlugin, ColumnStretchPlugin, RowOddPlugin];
theme = currentTheme().isDark() ? 'darkMaterial' : 'material';
additionalData = { // Define context menu rowContextMenu: createContextMenuConfig, stretch: 'all' };
ngOnInit() {}}// Context menu configuration for row operationsimport type { ContextMenuItem } from '@revolist/revogrid-pro';export interface ContextMenuConfig { items: ContextMenuItem[];}
// Buffer to store copied/cut row datalet rowBuffer: any = null;
export const createContextMenuConfig: ContextMenuConfig = { items: [ { icon: 'fa-solid fa-copy', name: 'Copy row', action: (_, cell, __, ____, { revogrid: grid }) => { if (!cell) return; // todo: it's virtual index, we need to convert it to physical index, it's not the same as the source index rowBuffer = { ...grid.source[cell.y] }; }, }, { icon: 'fa-solid fa-cut', name: 'Cut row', action: (_, cell, __, ____, { revogrid: grid }) => { if (!cell) return; rowBuffer = { ...grid.source[cell.y] }; // todo: it's virtual index, we need to convert it to physical index, it's not the same as the source index grid.source.splice(cell.y, 1); grid.source = [...grid.source]; }, }, { icon: 'fa-solid fa-paste', name: 'Paste row', hidden: () => !rowBuffer, action: (_, cell, __, ____, { revogrid: grid }) => { if (!cell || !rowBuffer) return; const newRow = { ...rowBuffer }; // todo: it's virtual index, we need to convert it to physical index // it's not the same as the source index grid.source.splice(cell.y + 1, 0, newRow); grid.source = [...grid.source]; }, }, { icon: 'fa-solid fa-arrow-up', name: 'Add row above', action: (_, cell, __, ____, { revogrid: grid }) => { if (!cell) { return; } // todo: it's virtual index, we need to convert it to physical index // it's not the same as the source index grid.source.splice(cell.y, 0, { id: 0, name: 'New row', price: 0, }); grid.source = [...grid.source]; }, }, { icon: 'fa-solid fa-arrow-down', name: 'Add row below', action: (_, cell, __, ____, { revogrid: grid }) => { if (!cell) { return; } // todo: it's virtual index, we need to convert it to physical index // it's not the same as the source index grid.source.splice(cell.y + 1, 0, { id: 0, name: 'New row', price: 0, }); grid.source = [...grid.source]; }, }, { icon: 'fa-solid fa-trash', name: (focused, range) => { if (!focused) { return ''; } if (!range) { range = { x: 0, y: focused.y, x1: 0, y1: focused.y, }; } // todo: it's virtual index, we need to convert it to physical index // it's not the same as the source index const rows = range.y1 - range.y + 1; if (!range || rows < 2) { return 'Delete row'; } return `Delete ${rows} rows`; }, action: (_, focused, range, __, { revogrid: grid }) => { if (!focused) { return; }
if (!range) { range = { x: 0, y: focused.y, x1: 0, y1: focused.y, }; }
const rows = range.y1 - range.y + 1; // todo: it's virtual index, we need to convert it to physical index // it's not the same as the source index grid.source.splice(range.y, rows); grid.source = [...grid.source]; }, } ]};