Right-click custom context menu

HI guys. I need to call event Right-click and open context menu. How do you do it?

Hello @Sank95,

Welcome to the community!

Assuming you know some JS, you just add the event listener to your component upon load.

The easiest way is to do this through a clientside callback:

app.clientside_callback(
"""function (id) {
    document.getElementById(id).addEventListener('contextmenu', myfunc())
    window.dash_clientside.no_update
}""", Output('component', 'id'), Input('component', 'id')
)

You can have your custom function in the assets folder in a js file and this will auto load.

2 Likes

Hello, how are you!

Following up on this question. What’s the easiest way to trigger a callback from a right-click context menu event.

Thanks!

You can have dash components inside the div, on right click you just toggle the location and the display of the div.

So, you’d have to use position absolute and then give it the left and top of the event.clientX and event.clientY.

There might also be some better components that you could use that might be able do some of this labor for you.

@Sank95 You can follow this demo, very advanced context menu:

https://fac.feffery.tech/AntdDropdown#自由位置模式

Hello @CNFeffery,

This is a cool component indeed. Unfortunately, it requires your target components to be something that exposes the contextMenu event, like your div.

Using a JS event listener allows for any component to be used.

There are a couple of tricks that we can use with components that are already available from Dash.

fuc.FefferyDiv could be used as any target’s container, for example, as the root container of the whole page, then creating a right-click menu anywhere will be as easy as drinking water.

How would you determine the event target for multiple components?

Wrap each and every one of the components inside the div?

if the isHovering of FefferyDiv is True, then we can be sure that the right-click event occurred in the target container.

Yes, you can tell that it occurred in the div container from the contextMenu.

But, you cant wrap two components like such with just one listener:

fef.FefferDiv([
    dcc.Input(id='1'), dcc.Input(id='2')
])

And have both components have different context menus.

In JS you can switch as such:

if (event.target.id == '1') { return context1 }
if (event.target.id == '2') { return context2 }

indeed, it’s pretty easy, without any javascript (by the way, I think dash’s core competency is that there is no need or little to write javascript, otherwise, why Dash):

图5

import dash
import json
from dash import html, dcc
import feffery_utils_components as fuc
from dash.dependencies import Input, Output

app = dash.Dash(__name__)

app.layout = html.Div(
    [
        html.Div(
            [
                fuc.FefferyDiv(
                    dcc.Input(
                        placeholder='input1'
                    ),
                    id='wrap-input1',
                    enableListenContextMenu=True,
                    style={
                        'display': 'inline'
                    }
                ),
                fuc.FefferyDiv(
                    dcc.Input(
                        placeholder='input2'
                    ),
                    id='wrap-input2',
                    enableListenContextMenu=True,
                    style={
                        'display': 'inline'
                    }
                )
            ]
        ),
        html.Pre(id='event-output')
    ],
    style={
        'padding': 50
    }
)


@app.callback(
    Output('event-output', 'children'),
    [Input('wrap-input1', 'contextMenuEvent'),
     Input('wrap-input2', 'contextMenuEvent')]
)
def show_event(contextMenuEvent1, contextMenuEvent2):

    return json.dumps(
        {
            'who trigger?': dash.ctx.triggered_id,
            'event': contextMenuEvent1 if dash.ctx.triggered_id == 'wrap-input1' else contextMenuEvent2
        },
        indent=4
    )


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

Writing an event listener is pretty straightforward, and well within scope of creating a custom context menu.

Yes, I am aware that you could wrap individual event listeners, the way that you just did.

I think the callback function in dash is a very genius design, which is both simple and versatile. If combined with a large number of functional components that can be implemented without javascript, dash will be favored by more python users. It’s way ahead of weird-conceptually confusing frameworks like streamlit, gradio, reflex, etc

1 Like

Hi guys. So, I want to show MRE. I want to clarify the task. I need call contextmenu for AntTree and want to know id clicked element and selected element in contextmeny.

from dash import dcc, html, dash_table, Input, Output, State, callback, clientside_callback, Patch
import dash_draggable
import pandas as pd
import dash
import feffery_antd_components as fac

app = dash.Dash(__name__)

app.layout = html.Div([
    dcc.Location(id="url"),
    dash_draggable.ResponsiveGridLayout(
        id='tests',
        clearSavedLayout=True,
        height=100,
        resizeHandles=["e"],
        compactType="horizontal",
        verticalCompact=False,

        layouts={
            "lg": [{
                "i": "tree_programming",
                "x": 0, "y": 0, "w": 2, "h": 7.5,
                "minW": 1, "maxW": 2

            },
                {
                    "i": "output_area",
                    "x": 2, "y": 0, "w": 10, "h": 7.5,
                    "minW": 6, "maxW": 10
                }
            ],
        },

        children=[
            html.Div(
                id='tree_programming',
            ),
            html.Div(
                id='output_area',
                children=[
                    dcc.Tabs(
                        id="tabs",
                        value='main',
                        children=[
                            dcc.Tab(
                                label="Default",
                                value='output_default',
                                id="output_default"
                            ),

                            dcc.Tab(
                                label="Show datasets",
                                value="area7",
                                id='output_flex'
                            )
                        ]
                    )
                ]
            ),
        ]
    )
])


@callback(
    [
        Output('tree_programming', 'children')
    ],
    [
        Input("url", 'pathname'),
        Input("url", 'search')
    ]
)
def create_tree(path, search):
    df_scope = pd.DataFrame({'x': [123, 42, 4235, 523, 35235, 523, 253], 'y': [12, 523, 53, 523, 5235235, 523, 1]})

    children_dataset = [{'title': i, 'key': 'key_' + i} for i in df_scope.columns]
    layout_tree = create_layout_tree(children_dataset)
    return [layout_tree]


def create_layout_tree(children_dataset):
    layout_tree = fac.AntdTree(
        id='tree_geomechanic',
        treeData=[
            {
                'title': 'Dataset',
                'key': 'dataset',
                'selectable': False,
                'children': [
                    {
                        'title': 'Dataset',
                        'key': 'df',
                        'children': children_dataset
                    },
                ]
            },
        ],
        multiple=True,
        height=600,
    )
    return layout_tree

if __name__ == '__main__':
    #waitress.serve(server, listen='0.0.0.0:8080', threads=24)
    app.run_server(debug=False, host='127.0.0.1', port=83, threaded=True)

AntdTree has the built-in function of right-clicking nodes.

image

https://fac.feffery.tech/AntdTree#节点右键菜单回调示例

This answer solved the problem

A post was split to a new topic: Context Menu on Cytoscape