Context Menu

Customize the Jspreadsheet context menus by defining a contextMenu handler function. This handler receives default menu items, section information, coordinates, and event data, enabling the creation of dynamic, context-aware menu entries based on the clicked section and current application state.

What's new with Version 13

  • Nested headers: the nested header section offers Rename, Split column and Merge columns, and a right-click on a group already inside the selection keeps the selection so an action applies to every selected group
  • Paste options: the paste special entries map to the new paste options, values, numberFormat, transpose and formatOnly

Documentation

Event Handler

contextMenu(worksheet, x, y, e, items, section, a1, a2) => Array | boolean

Parameter Type Description
worksheet Object Instance of the active worksheet
x Number Column number
y Number Row number
e Object Mouse event handler
items Array Default context menu items
section String Click location: nested-header | header | row | cell | select-all | tabs | media. Each of these sections has its own default items. A right-click on any other area of the spreadsheet, such as toolbar or footer, also calls the handler with that area as section, but items then contains only Save as and About
a1 Any Column coordinate of the clicked element, or null when it has none: the column for cell and header, the group index for nested-header, the media element for media, null for row and select-all
a2 Any Row coordinate of the clicked element, or null when it has none: the row for cell and row, the nested header row for nested-header, null for header, select-all and media

Returns: An array of menu item objects to replace the default items, false to cancel the menu, or nothing to keep the default items.

The contextMenu handler is a spreadsheet option and applies to every worksheet. To suppress the menu entirely, return false from the handler.

Menu Item Properties

The context menu handler should return an array of menu item objects. Each menu item can include the following properties:

Property Description
type: string Menu item type: line | default. A line item renders a separator and ignores the other properties
title: string Menu item display title
icon: string Material Design icon key for the menu item
disabled: boolean Whether the menu item is disabled
shortcut: string Keyboard shortcut description (typically displayed on the right)
tooltip: string Tooltip text shown on hover
submenu: Array of items Array of submenu items for nested menus
onclick: function Click event handler for the menu item
onclick(e: MouseEvent, element: HTMLElement) => void
onopen: function When the context menu item opens
onclose: function When the context menu item closes

Translation

Context menu text can be translated using jspreadsheet.setDictionary to define translations for English terms.

jspreadsheet.setDictionary({
    'Rename this worksheet': 'Renomear worksheet',
    'Delete this worksheet': 'Apagar worksheet',
    'Are you sure?': 'Tem certeza?',
    'Rename this cell': 'Renomear essa celula',
    'Cut': 'Cortar',
    'Copy': 'Copiar',
    'Paste': 'Colar',
    'Insert a new column before': 'Inserir uma coluna antes',
    'Insert a new column after': 'Inserir uma coluna depois',
    'Delete selected columns': 'Apagar colunas selecionadas',
    'Rename this column': 'Renomar essa coluna',
    'Create a new row': 'Criar uma nova linha',
    'Order ascending': 'Ordenar asc',
    'Order descending': 'Ordenar desc',
    'Insert a new row before': 'Inserir uma linha antes',
    'Insert a new row after': 'Inserir uma nova linha depois',
    'Delete selected rows': 'Apagar linhas selecionadas',
    'Edit notes': 'Editar notas',
    'Add notes': 'Adicionar notas',
    'Notes': 'Notas',
    'Clear notes': 'Apagar notas',
    'Save as': 'Salvar como',
    'About': 'Sobre',
});

Jspreadsheet uses the LemonadeJS context menu component. For additional details and examples, visit: Lemonade

Examples

Disable the context menu

To completely disable the context menu, define a contextMenu handler that returns false.

<html>
<script src="https://jspreadsheet.com/v13/jspreadsheet.js"></script>
<link rel="stylesheet" href="https://jspreadsheet.com/v13/jspreadsheet.css" type="text/css" />
<script src="https://jsuites.net/v6/jsuites.js"></script>
<link rel="stylesheet" href="https://jsuites.net/v6/jsuites.css" type="text/css" />

<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons" />

<div id="spreadsheet"></div>

<script>
// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('ZGQ3MDI4ZDBiYjlmMDExZTEwMDZmM2RmOTU5ZDI3MTQ2YjBiYjAxNTUxYWI3YzQxYjYzY2UyMGI5NWRmNzY4ODc5NTJkYjFhOTZhNDVkYjdjMjZlYzZiN2I3NWVlYTg4YWJkMWYzZDUyMDJiMDI3YzYwOGU4NGUzNTcwZDVlMWUsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprek56azBPRGcyTENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

// Create the spreadsheet
jspreadsheet(document.getElementById('spreadsheet'), {
    worksheets: [{
        minDimensions: [4,4],
    }],
    contextMenu: function() {
        return false;
    }
});
</script>
</html>
import React, {useRef} from "react";
import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet/react";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";

// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('ZGQ3MDI4ZDBiYjlmMDExZTEwMDZmM2RmOTU5ZDI3MTQ2YjBiYjAxNTUxYWI3YzQxYjYzY2UyMGI5NWRmNzY4ODc5NTJkYjFhOTZhNDVkYjdjMjZlYzZiN2I3NWVlYTg4YWJkMWYzZDUyMDJiMDI3YzYwOGU4NGUzNTcwZDVlMWUsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprek56azBPRGcyTENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

export default function App() {
    // Spreadsheet array of worksheets
    const spreadsheet = useRef();

    // Disable the context menu
    const contextMenu = () => {
        return false;
    };

    return (
        <Spreadsheet ref={spreadsheet} contextMenu={contextMenu}>
            <Worksheet minDimensions={[10, 10]}/>
        </Spreadsheet>
    );
}
<template>
    <Spreadsheet ref="spreadsheet" :contextMenu="contextMenu">
        <Worksheet :minDimensions="[4,4]" />
    </Spreadsheet>
</template>

<script>
import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet/vue";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";

// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('ZGQ3MDI4ZDBiYjlmMDExZTEwMDZmM2RmOTU5ZDI3MTQ2YjBiYjAxNTUxYWI3YzQxYjYzY2UyMGI5NWRmNzY4ODc5NTJkYjFhOTZhNDVkYjdjMjZlYzZiN2I3NWVlYTg4YWJkMWYzZDUyMDJiMDI3YzYwOGU4NGUzNTcwZDVlMWUsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprek56azBPRGcyTENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

export default {
    components: {
        Spreadsheet,
        Worksheet,
    },
    data() {
        // Disable the context menu
        const contextMenu = () => {
            return false;
        };

        return {
            contextMenu
        };
    }
}
</script>
import { Component, ViewChild, ElementRef } from "@angular/core";
import jspreadsheet from "jspreadsheet";
import "jspreadsheet/dist/jspreadsheet.css"
import "jsuites/dist/jsuites.css"

// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('ZGQ3MDI4ZDBiYjlmMDExZTEwMDZmM2RmOTU5ZDI3MTQ2YjBiYjAxNTUxYWI3YzQxYjYzY2UyMGI5NWRmNzY4ODc5NTJkYjFhOTZhNDVkYjdjMjZlYzZiN2I3NWVlYTg4YWJkMWYzZDUyMDJiMDI3YzYwOGU4NGUzNTcwZDVlMWUsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprek56azBPRGcyTENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

// Create component
@Component({
    standalone: true,
    selector: "app-root",
    template: `<div #spreadsheet></div>`,
})
export class AppComponent {
    @ViewChild("spreadsheet") spreadsheet: ElementRef;
    // Worksheets
    worksheets: jspreadsheet.worksheetInstance[];
    // Create a new data grid
    ngAfterViewInit() {
        // Create spreadsheet
        this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, {
            worksheets: [{
                minDimensions: [4,4],
            }],
            // Disable the context menu
            contextMenu: function() {
                return false;
            }
        });
    }
}

Customize the context menu

This example demonstrates how to customize context menu options based on the clicked section and application context.

<html>
<script src="https://jspreadsheet.com/v13/jspreadsheet.js"></script>
<script src="https://jsuites.net/v6/jsuites.js"></script>
<link rel="stylesheet" href="https://jspreadsheet.com/v13/jspreadsheet.css" type="text/css" />
<link rel="stylesheet" href="https://jsuites.net/v6/jsuites.css" type="text/css" />

<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons" />

<div id="spreadsheet"></div>

<script>
// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('ZGQ3MDI4ZDBiYjlmMDExZTEwMDZmM2RmOTU5ZDI3MTQ2YjBiYjAxNTUxYWI3YzQxYjYzY2UyMGI5NWRmNzY4ODc5NTJkYjFhOTZhNDVkYjdjMjZlYzZiN2I3NWVlYTg4YWJkMWYzZDUyMDJiMDI3YzYwOGU4NGUzNTcwZDVlMWUsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprek56azBPRGcyTENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

// Create a new spreadsheet
jspreadsheet(document.getElementById('spreadsheet'), {
    worksheets: [{
        data: [
            ["Apples", "Produce", 1.5, 10, "=C1*(1-(D1/100))"],
            ["Bread", "Bakery", 3.0, 20, "=C2*(1-(D2/100))"],
            ["Cheese", "Dairy", 5.0, 15, "=C3*(1-(D3/100))"],
            ["Eggs", "Dairy", 2.5, 0, "=C4*(1-(D4/100))"],
        ],
        columns: [
            { title: 'Food', width: '140px' },
            { title: 'Category' },
            { title: 'Unit Price' },
            { title: 'Discount' },
            { title: 'Total' },
         ],
         allowComments: true,
    }],
    contextMenu: function(o, x, y, e, items, section) {
         // Reset all items
         let itemsArr = [];

         // If the click was in the headers
         if (section == 'header') {
            // Items to the header only
            itemsArr.push({
                title: jSuites.translate('Execute one action'),
                onclick: function() {
                    alert('test')
                }
            });

            // Add a line
            itemsArr.push({ type: 'line' });
         }

         // Save
         itemsArr.push({
             title: jSuites.translate('Save as'),
             shortcut: 'Ctrl + S',
             icon: 'save',
             onclick: function () {
                 o.download();
             }
         });

         // About
         itemsArr.push({
             title: jSuites.translate('About'),
             onclick: function() {
                 alert('https://jspreadsheet.com');
             }
         });

         return itemsArr;
     }
});
</script>
</html>
import React, { useRef } from "react";
import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet/react";
import jSuites from "jsuites";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";

// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('ZGQ3MDI4ZDBiYjlmMDExZTEwMDZmM2RmOTU5ZDI3MTQ2YjBiYjAxNTUxYWI3YzQxYjYzY2UyMGI5NWRmNzY4ODc5NTJkYjFhOTZhNDVkYjdjMjZlYzZiN2I3NWVlYTg4YWJkMWYzZDUyMDJiMDI3YzYwOGU4NGUzNTcwZDVlMWUsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprek56azBPRGcyTENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

// Intercept the context menu
const contextMenu = (o, x, y, e, items, section) => {
     // Reset all items
     items = [];

     // If the click was in the headers
     if (section == 'header') {
        // Items to the header only
        items.push({
            title: jSuites.translate('Execute one action'),
            onclick: function() {
                alert('test')
            }
        });

        // Add a line
        items.push({ type: 'line' });
     }

     // Save
     items.push({
         title: jSuites.translate('Save as'),
         shortcut: 'Ctrl + S',
         icon: 'save',
         onclick: function () {
             o.download();
         }
     });

     // About
     items.push({
         title: jSuites.translate('About'),
         onclick: function() {
             alert('https://jspreadsheet.com');
         }
     });

     return items;
}

export default function App() {
    // Spreadsheet array of worksheets
    const spreadsheet = useRef();
    // Data
    const data = [
        ["Apples", "Produce", 1.5, 10, "=C1*(1-(D1/100))"],
        ["Bread", "Bakery", 3.0, 20, "=C2*(1-(D2/100))"],
        ["Cheese", "Dairy", 5.0, 15, "=C3*(1-(D3/100))"],
        ["Eggs", "Dairy", 2.5, 0, "=C4*(1-(D4/100))"],
    ];
    // Columns
    const columns = [
        { title: 'Food', width: '140px' },
        { title: 'Category' },
        { title: 'Unit Price' },
        { title: 'Discount' },
        { title: 'Total' },
    ];

    return (
        <Spreadsheet ref={spreadsheet} contextMenu={contextMenu}>
            <Worksheet data={data} columns={columns} />
        </Spreadsheet>
    );
}
<template>
    <Spreadsheet ref="spreadsheet" :contextMenu="contextMenu">
        <Worksheet :data="data" :columns="columns" />
    </Spreadsheet>
</template>

<script>
import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet/vue";
import jSuites from "jsuites";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";

// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('ZGQ3MDI4ZDBiYjlmMDExZTEwMDZmM2RmOTU5ZDI3MTQ2YjBiYjAxNTUxYWI3YzQxYjYzY2UyMGI5NWRmNzY4ODc5NTJkYjFhOTZhNDVkYjdjMjZlYzZiN2I3NWVlYTg4YWJkMWYzZDUyMDJiMDI3YzYwOGU4NGUzNTcwZDVlMWUsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprek56azBPRGcyTENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

// Intercept the context menu
const contextMenu = (o, x, y, e, items, section) => {
     // Reset all items
     items = [];

     // If the click was in the headers
     if (section == 'header') {
        // Items to the header only
        items.push({
            title: jSuites.translate('Execute one action'),
            onclick: function() {
                alert('test')
            }
        });

        // Add a line
        items.push({ type: 'line' });
     }

     // Save
     items.push({
         title: jSuites.translate('Save as'),
         shortcut: 'Ctrl + S',
         icon: 'save',
         onclick: function () {
             o.download();
         }
     });

     // About
     items.push({
         title: jSuites.translate('About'),
         onclick: function() {
             alert('https://jspreadsheet.com');
         }
     });

     return items;
}

export default {
    components: {
        Spreadsheet,
        Worksheet,
    },
    data() {
        // Data
        const data = [
            ["Apples", "Produce", 1.5, 10, "=C1*(1-(D1/100))"],
            ["Bread", "Bakery", 3.0, 20, "=C2*(1-(D2/100))"],
            ["Cheese", "Dairy", 5.0, 15, "=C3*(1-(D3/100))"],
            ["Eggs", "Dairy", 2.5, 0, "=C4*(1-(D4/100))"],
        ];
        // Columns
        const columns = [
            { title: 'Food', width: '140px' },
            { title: 'Category' },
            { title: 'Unit Price' },
            { title: 'Discount' },
            { title: 'Total' },
        ];

        return {
            contextMenu,
            data,
            columns
        };
    }
}
</script>
import { Component, ViewChild, ElementRef } from "@angular/core";
import jspreadsheet from "jspreadsheet";

// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('ZGQ3MDI4ZDBiYjlmMDExZTEwMDZmM2RmOTU5ZDI3MTQ2YjBiYjAxNTUxYWI3YzQxYjYzY2UyMGI5NWRmNzY4ODc5NTJkYjFhOTZhNDVkYjdjMjZlYzZiN2I3NWVlYTg4YWJkMWYzZDUyMDJiMDI3YzYwOGU4NGUzNTcwZDVlMWUsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprek56azBPRGcyTENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

// Create component
@Component({
  standalone: true,
  selector: 'app-root',
  template: `<div #spreadsheet></div>`,
})
export class AppComponent {
  @ViewChild('spreadsheet') spreadsheet: ElementRef;
  // Worksheets
  worksheets: jspreadsheet.worksheetInstance[];
  // Create a new data grid
  ngAfterViewInit() {
    // Create spreadsheet
    this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, {
      worksheets: [
        {
          data: [
            ['Apples', 'Produce', 1.5, 10, '=C1*(1-(D1/100))'],
            ['Bread', 'Bakery', 3.0, 20, '=C2*(1-(D2/100))'],
            ['Cheese', 'Dairy', 5.0, 15, '=C3*(1-(D3/100))'],
            ['Eggs', 'Dairy', 2.5, 0, '=C4*(1-(D4/100))'],
          ],
          columns: [
            { title: 'Food', width: 140 },
            { title: 'Category' },
            { title: 'Unit Price' },
            { title: 'Discount' },
            { title: 'Total' },
          ],
          allowComments: true,
        },
      ],
      contextMenu: function (o, x, y, e, items, section) {
        // Reset all items
        items = [];

        // If the click was in the headers
        if (section == 'header') {
          // Items to the header only
          items.push({
            title: 'Execute one action',
            onclick: function () {
              alert('test');
            },
          });

          // Add a line
          items.push({ type: 'line' });
        }

        // Save
        items.push({
          title: 'Save as',
          shortcut: 'Ctrl + S',
          icon: 'save',
          onclick: function () {
            o.download();
          },
        });

        // About
        items.push({
          title: 'About',
          onclick: function () {
            alert('https://jspreadsheet.com');
          },
        });

        return items;
      },
    });
  }
}

Default context menu implementation

This is the reference implementation of the default context menu handler showing how different sections and conditions are handled.

/**
 * Translation helper
 */
function T(t) {
    return jSuites.translate(t);
}

/**
 * Default context menu items
 * @param {object} obj - worksheet
 * @param {number} x
 * @param {number} y
 * @param {object} event
 * @param {object} items
 * @param {string} section
 * @param {object\|number} argument1
 * @param {number} argument2
 * @return {array} Return the items to create the contextmenu
 */
function contextmenu(obj, x, y, e, items, section, a1, a2) {
    // Reset all default items
    items = [];
    
    let o = obj.parent.config;

    // Is mac
    let ctrl = (navigator.userAgent.indexOf("Mac") !== -1) ? '⌘' : 'Ctrl';

    // Click in the tabs
    if (section === 'tabs') {
        if (o.allowRenameWorksheet !== false) {
            items.push({
                title: T('Rename this worksheet'),
                onclick: function() {

                    let newName = prompt(T('Rename this worksheet'), e.target.textContent);
                    if (newName) {
                        obj.parent.renameWorksheet(a1, newName);
                    }
                }
            });
        }

        if (o.allowDeleteWorksheet !== false) {
            items.push({
                title: T('Delete this worksheet'),
                onclick: function() {
                    if (confirm(T('Are you sure?'), e.target.textContent)) {
                        obj.parent.deleteWorksheet(a1);
                    }
                }
            });
        }

        items.push({ type:'line' });
    }

    // Nested header only
    if (section === 'nested-header') {
        // Rename nested headers
        items.push({
            title: T('Rename this cell'),
            onclick: function() {
                // Get the new title
                let text = prompt(T('Rename this cell'), e.target.textContent);
                // Update the nested cell title
                obj.setNestedCell(a1, a2, { title: text });
            }
        });

        items.push({ type:'line' });
    }

    // Sections that affect the selection
    if (section === 'header' || section === 'row' || section === 'cell' || section === 'nested-header') {
        // Cut
        items.push({
            title: T('Cut'),
            icon: 'content_cut',
            shortcut: ctrl + ' + X',
            onclick: function() {
                obj.cut();
            }
        });

        // Copy
        items.push({
            title: T('Copy'),
            icon: 'content_copy',
            shortcut: ctrl + ' + C',
            onclick: function() {
                obj.copy();
            }
        });

        // Paste
        if (navigator && navigator.clipboard && navigator.clipboard.readText) {
            items.push({
                title: T('Paste'),
                icon: 'content_paste',
                shortcut: ctrl + ' + V',
                onclick: function() {
                    if (obj.selectedCell) {
                        navigator.clipboard.readText().then(function(text) {
                            if (text) {
                                let px = Math.min(obj.selectedCell[0], obj.selectedCell[2]);
                                let py = Math.min(obj.selectedCell[1], obj.selectedCell[3]);
                                obj.paste(px, py, text);
                            }
                        });
                    }
                }
            });
        }

        items.push({ type:'line' });
    }

    // Clicking in the headers
    if (section === 'header') {
        // Insert a new column
        if (obj.options.allowInsertColumn !== false) {
            items.push({
                title: T('Insert a new column before'),
                onclick: function() {
                    obj.insertColumn(1, parseInt(a1), 1);
                }
            });

            items.push({
                title: T('Insert a new column after'),
                onclick: function() {
                    obj.insertColumn(1, parseInt(a1), 0);
                }
            });
        }

        // Delete a column
        if (obj.options.allowDeleteColumn !== false) {
            items.push({
                title: T('Delete selected columns'),
                onclick: function() {
                    obj.deleteColumn(obj.getSelectedColumns());
                }
            });
        }

        // Rename column
        if (obj.options.allowRenameColumn !== false) {
            items.push({
                title: T('Rename this column'),
                onclick: function() {
                    obj.setHeader(a1);
                }
            });
        }

        // Append new row
        if (! obj.options.data.length) {
            // Line
            items.push({ type:'line' });

            items.push({
                title: T('Create a new row'),
                onclick: function() {
                    obj.insertRow(0);
                }
            });
        }

        // Sorting
        if (obj.options.columnSorting !== false) {
            // Line
            items.push({ type:'line' });

            items.push({
                title: T('Order ascending'),
                onclick: function() {
                    obj.orderBy(a1, 0);
                }
            });
            items.push({
                title: T('Order descending'),
                onclick: function() {
                    obj.orderBy(a1, 1);
                }
            });

            items.push({ type:'line' });
        }

        items.push({
            title: T('Hide'),
            onclick: function() {
                obj.hideColumn(obj.getSelectedColumns());
            }
        });

        items.push({
            title: T('Show'),
            onclick: function() {
                obj.showColumn(obj.getSelectedColumns());
            }
        });

        items.push({ type:'line' });
    }

    // Row only
    if (section === 'cell' || section === 'row') {
        // Insert new row
        if (obj.options.allowInsertRow !== false) {
            items.push({
                title: T('Insert a new row before'),
                onclick: function() {
                    obj.insertRow(1, parseInt(y), 1);
                }
            });

            items.push({
                title: T('Insert a new row after'),
                onclick: function() {
                    obj.insertRow(1, parseInt(y));
                }
            });
        }

        if (obj.options.allowDeleteRow !== false) {
            items.push({
                title: T('Delete selected rows'),
                onclick: function() {
                    obj.deleteRow(obj.getSelectedRows(true));
                }
            });
        }

        items.push({ type:'line' });
    }

    if (section === 'row') {
        items.push({
            title: T('Hide'),
            onclick: function() {
                obj.hideRow(obj.getSelectedRows());
            }
        });

        items.push({
            title: T('Show'),
            onclick: function() {
                obj.showRow(obj.getSelectedRows());
            }
        });

        items.push({ type:'line' });
    }

    // Click in the cell
    if (section === 'cell') {
        if (obj.options.allowComments !== false) {
            let title = obj.records[a2][a1].element.getAttribute('title') || '';

            items.push({
                title: title ? T('Edit notes') : T('Add notes'),
                icon: 'notes',
                onclick: function() {
                    let comment = prompt(T('Notes'), title);
                    if (comment) {
                        let cell = jspreadsheet.helpers.getColumnNameFromCoords(a1, a2);
                        obj.setComments(cell, comment);
                    }
                }
            });

            if (title) {
                items.push({
                    title: T('Clear notes'),
                    onclick: function() {
                        let cell = jspreadsheet.helpers.getColumnNameFromCoords(a1, a2);
                        obj.setComments(cell, '');
                    }
                });
            }

            // Line
            items.push({ type:'line' });
        }
    }

    if (section === 'select-all') {
        items.push({
            title: T('Show all'),
            onclick: function() {
                obj.showRow(obj.getSelectedRows());
                obj.showColumn(obj.getSelectedColumns());
            }
        });

        // Line
        items.push({ type:'line' });
    }

    // Save
    if (o.allowExport !== false) {
        items.push({
            title: T('Save as'),
            icon: 'save',
            shortcut: ctrl + ' + S',
            onclick: function () {
                obj.download();
            }
        });
    }

    // About
    if (o.about !== false) {
        items.push({
            title: T('About'),
            icon: 'info',
            onclick: function() {
                alert(jspreadsheet.version().print());
            }
        });
    }

    return items;
}

Plugin-based Customization

For complex applications, plugins offer a modular approach to context menu customization. This enables code reuse and better maintainability across multiple spreadsheet instances.

const myContextMenuPlugin = {
    contextMenu: function(worksheet, x, y, e, items, section, a1, a2) {
        // Custom menu logic here
        return items;
    }
};

// Apply plugin to spreadsheet
jspreadsheet(element, {
    plugins: { myContextMenuPlugin },
    // other options...
});

Learn more about creating plugins

See Also