Context Menu in Dash AG Grid

Hi All,

I have been doing some experimentation with Dash Ag-Grid and I am totally loving it. I have also taken a trial license key from Ag-Grid folk to test some features which are available exclusively for enterprise version of Ag-Grid.

Context Menus are one such feature. The context menu appears by default when you enable enterprise version of Ag-Grid. and it looks like below:

I am wondering if anyone was able to successfully customize the context menu and handle the callbacks as well? I have tried looking into Ag-Grid Javascript documentation and tried few options, but no success so far. I am probably paralyzed here because of lack of JavaScript / React knowledge. Any help in this regard will be highly appreciated.

Thanks!

Hello @smalgotrader,

Welcome to the community!

What functionality have you tried so far? A lot of those things are available by default on the grid without needing callbacks.

1 Like

Thanks for your message!

I am building a live data streaming app for Stocks. In the background, there is a list of objects which updates the live market data for various stocks that I have subscribed through using an external api. The Ag Grid displays this list of objects. I am updating the grid at regular frequency using dcc.interval component.

One use case is an option on the context menu called Unsubscribe market data, this will call the external api to cancel the subscription for the stock on whose row context menu was opened.

The alternative way to do this is to have cell renderer component in the form of a button in a column in the grid. This will work, but I have space constraint on the screen (since this grid is just one of the many things being displayed on the page). I don’t want to have one column each for various functionalities that I am planning to add.

Thanks

Hi @smalgotrader
Have you tried the RowMenu component?

2 Likes

Hi @AnnMarieW,

Yes, RowMenu is another option, but similar to button component, it requires another column. I want the grid to be more compact and take as less space as possible, so that other stuff can fit in the screen.

I feel context menu is more intuitive than having to scroll to the right to find the column to initiate action.

Thanks for your suggestion!

The context menu is a nice feature and looks great too!

It’s on the list to document this and all the other AG Grid Enterprise features. I’ll prioritize making a custom context menu example and will post it here when it’s ready.

2 Likes

Thank you so much @AnnMarieW!

Hello @smalgotrader,

With dash ag grid 2.2.0, you now have access to the api and columnApi.

The api allows you to add custom event listeners, now, this won’t directly trigger a callback, but you can definitely hook it into something to trigger it.

You could use the cellContextMenu event, and make sure the column matches the column you want, if so. Prevent the default and show your custom menu where they can disable or enable the updates.

To add it, do something like this:

dash_ag_grid.getApi(“id”).addEverListener(“cellContextMenu”, customContext)

Where customContext is your custom Js function.

I too would be super interested in an example! It’s been bugging me for ages - tried using some of the existing js implementation examples but it doesn’t seem to work.

Hi @LethalNote and welcome to the Dash community :slight_smile:

Currently the getContextMenuItems function isn’t available in Dash AG Grid yet. This makes it easier to customize the context menu items and provide custom functions. I’m hoping it will be in a future release.

@LethalNote and @smalgotrader

:tada: Dash AG Grid 2.4.0 is now available

It’s now possible to customize the context menu (An AG Grid Enterprise feature). Here are 2 examples.

1. Customize the Context Menu

This is a Dash version of an example in the AG Grid docs.

ag-grid-context

import dash_ag_grid as dag
from dash import Dash, html
import pandas as pd

app = Dash(__name__)


df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)

columnDefs = [
    {"field": i} for i in ["country", "year", "athlete", "sport", "total"]
]

app.layout = html.Div(
    [
        dag.AgGrid(
            columnDefs=columnDefs,
            rowData=df.to_dict("records"),
            dashGridOptions={
                "allowContextMenuWithControlKey": True,
                "getContextMenuItems" :{"function": "getContextMenuItems(params)"}
            },
            defaultColDef=dict(
                resizable=True,
                filter=True,
                sortable=True,
            ),
            id="grid-context",
            enableEnterpriseModules=True,

        ),
    ]
)


if __name__ == "__main__":
    app.run(debug=True)


Add the following to the dashAgGridFunctions.js file in the assets folder

var dagfuncs = (window.dashAgGridFunctions = window.dashAgGridFunctions || {});
dagfuncs.getContextMenuItems = (params) => {

    function createFlagImg(flag) {
      return (
        '<img border="0" width="15" height="10" src="https://flags.fmcdn.net/data/flags/mini/' +
        flag +
        '.png"/>'
      );
    }

    var result = [
      {
        // custom item
        name: 'Alert ' + params.value,
        action: () => {
          window.alert('Alerting about ' + params.value);
        },
        cssClasses: ['redFont', 'bold'],
      },
      {
        // custom item
        name: 'Always Disabled',
        disabled: true,
        tooltip:
          'Very long tooltip, did I mention that I am very long, well I am! Long!  Very Long!',
      },
      {
        name: 'Country',
        subMenu: [
          {
            name: 'Ireland',
            action: () => {
              console.log('Ireland was pressed');
            },
            icon: createFlagImg('ie'),
          },
          {
            name: 'UK',
            action: () => {
              console.log('UK was pressed');
            },
            icon: createFlagImg('gb'),
          },
          {
            name: 'France',
            action: () => {
              console.log('France was pressed');
            },
            icon: createFlagImg('fr'),
          },
        ],
      },
      {
        name: 'Person',
        subMenu: [
          {
            name: 'Niall',
            action: () => {
              console.log('Niall was pressed');
            },
          },
          {
            name: 'Sean',
            action: () => {
              console.log('Sean was pressed');
            },
          },
          {
            name: 'John',
            action: () => {
              console.log('John was pressed');
            },
          },
          {
            name: 'Alberto',
            action: () => {
              console.log('Alberto was pressed');
            },
          },
          {
            name: 'Tony',
            action: () => {
              console.log('Tony was pressed');
            },
          },
          {
            name: 'Andrew',
            action: () => {
              console.log('Andrew was pressed');
            },
          },
          {
            name: 'Kev',
            action: () => {
              console.log('Kev was pressed');
            },
          },
          {
            name: 'Will',
            action: () => {
              console.log('Will was pressed');
            },
          },
          {
            name: 'Armaan',
            action: () => {
              console.log('Armaan was pressed');
            },
          },
        ],
      },
      'separator',
      {
        // custom item
        name: 'Windows',
        shortcut: 'Alt + W',
        action: () => {
          console.log('Windows Item Selected');
        },
        icon:
          '<img src="https://www.ag-grid.com/example-assets/skills/windows.png" />',
      },
      {
        // custom item
        name: 'Mac',
        shortcut: 'Alt + M',
        action: () => {
          console.log('Mac Item Selected');
        },
        icon:
          '<img src="https://www.ag-grid.com/example-assets/skills/mac.png"/>',
      },
      'separator',
      {
        // custom item
        name: 'Checked',
        checked: true,
        action: () => {
          console.log('Checked Selected');
        },
        icon:
          '<img src="https://www.ag-grid.com/example-assets/skills/mac.png"/>',
      },
      'copy',
      'separator',
      'chartRange',
    ];
    return result;
  };

2. Custom Context Menu with Dash Callbacks

Shows how how to add custom features to the context menu such as:

  • Accessing the AG Grid API – the example clears all the filters
  • Custom item - open a new tab with a link created from cell data
  • Trigger a Dash Callback

ag-grid-context-callback

import dash_ag_grid as dag
from dash import Dash, html, callback, Input, Output, State
import pandas as pd
import  dash_bootstrap_components as dbc

app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP, dbc.icons.FONT_AWESOME])


df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)

columnDefs = [
    {"field": i} for i in ["country", "year", "athlete", "sport", "total"]
]

app.layout = html.Div(
    [
        html.Button(id="stop", style={"display": "none"}),
        dag.AgGrid(
            columnDefs=columnDefs,
            rowData=df.to_dict("records"),
            dashGridOptions={
                "allowContextMenuWithControlKey": True,
                "getContextMenuItems" :{"function": "getContextMenuItemsCallback(params)"}
            },
            defaultColDef=dict(
                resizable=True,
                filter=True,
                sortable=True,
            ),
            id="grid-context",
            enableEnterpriseModules=True,
        ),
        html.Div(id="output")
    ]
)

@callback(
    Output("output", "children"),
    Input("stop", "n_clicks"),
)
def update(n):
    return f"Stop clicked {n} times."


if __name__ == "__main__":
    app.run(debug=True)

Add the following to the dashAgGridFunctions.js file in the assets folder

var dagfuncs = (window.dashAgGridFunctions = window.dashAgGridFunctions || {});

dagfuncs.getContextMenuItemsCallback = (params) => {
    var result = [
      {
        // access grid's API
        name: 'Clear All Filters',
        action: () => {
          params.api.setFilterModel(null);
        },
        icon: '<i class="fa-solid  fa-square-minus" />'
      },
      {
        // custom item
        name: 'Look up ' + params.value,
        action: () => {
          window.open("https://en.wikipedia.org/wiki/" + params.value, '_blank').focus();
        },
        icon: '<i class="fa-solid  fa-magnifying-glass" />'
      },
       {
        // trigger a callback
        name: 'Stop process',
        action: () => {
          const stopBtn = document.getElementById("stop");          
          stopBtn.click();
        },
        icon: '<i class="fa-solid  fa-stop" />'
      },
      'copy',
      'separator',
      'chartRange',
    ];
    return result;
  };



dag-docs

4 Likes

Very helpful examples. Thank you for sharing @AnnMarieW :pray:

1 Like

This is amazing, thank you!

1 Like

Hello,

I would like to pass the current params.value from the client to the callback server side, is it possible ?

Thanks a lot,

Thank you very much for the example.

Regarding the example triggering a callback. Is there a way to know which cell was triggered?
I tried using the “cellClicked” property of the table but it does not change by right click (only by left click).

Thanks @AnnMarieW, albeit a little late!

This is very cool , thanks @AnnMarieW for the examples! I’ve been toying around for some time now trying to pass the rowData via a callback, without much success. I’ve been able to populate a div with the data using params.node.data, however the second callback in the chain doesn’t recognize that the div has been changed. Perhaps this is now easier with the release of Dash 2.17 being able to set props? Is there a way to send data via a callback when clicking on the contextMenuItem?

Hello @latshawk,

To set_props from the clientside, this was introduced in version 2.16 and is at the namespace: dash_clientside.set_props the way to send data is the same as how its Python counterpart was designed. :slight_smile:

For more info on set_props see:

2 Likes