Use dropdown link menu in Dash AG Grid

We’re trying to replace some standard HTML tables in our Dash app with Dash AG Grid. One thing I want to have in each row is a dropdown menu of links.

I am aware of the rowMenu cell renderer, but it has a few deficiencies for my use case:

  • doesn’t appear to support customizing the button appearance. I would like to have standard bootstrap dropdown button appearance with an icon and text instead of the generic + svg that DAG uses
  • doesn’t appear to support rich formatting within the menu itself (only a text label and associated value). Maybe I could use a callback to make this work but since these are external links that should open in a new tab, I’d rather not do that unless there is no other alternative.

Here is a picture of what I am trying to achieve (the way a menu looks in our current HTML table using DBC DropDownMenu):

I’ve started writing a custom cell renderer to try to do this, but I’m getting stuck.

This is what I have so far:


const iconText = (icon, text, children) => {
    return React.createElement(
        'div',
        null,
        React.createElement(
            'i',
            { class: 'me-1 ' + icon },
        ),
        text,
        children
    );
}

dagcomponentfuncs.DropdownLinks = function (props) {
    return React.createElement(
        'div',
        { className: 'dropdown show' },
        React.createElement(
            'button',
            {
                className: "me-1 btn btn-primary btn-sm",
            },
            iconText("bi-link-45deg", "Links ", React.createElement(
                'i',
                { className: 'me-1 bi bi-caret-down-fill' },
            )),
        ),
        React.createElement(
            'div',
            { className: "dropdown-menu show" },
            props.value.map(([icon, title, url]) =>
                React.createElement(
                    'a',
                    { href: url },
                    iconText(icon, title)
                )
            )
        )
    )

};

This doesn’t implement the logic to toggle the dropdown menu currently but I was going to try to just get it to show properly and then figure out the toggling. I noted that for a DBC DropdownMenu, when I toggle the menu, “show” gets added to the CSS class, so that is what I have tried to do above to make it visible, but it’s not showing up.

As you can see in this screenshot, it’s positioning the dropdown correctly but it seems to be invisible or behind the AG Grid. (Note: I have removed the URLs from the a href elements in the inspector screenshot but they are correctly populated.

Any suggestions, please?

Hello @jblang,

You should be able to apply a zIndex to the dropdown component, and that should bring it in front. :slight_smile:

Using a cellRenderer, I think this is the only way to do it.

If using an editor, you can provide cellEditorPopup: true in order to bring it out.

In Chrome’s inspector, I checked and the --bs-dropdown-zindex of the div containing the links is already set to 1000. I also see the display: block style is being applied to it from the show css class that I added.

I’m not sure what else could be the problem. I’m honestly pretty frontend-inept–that’s why I use dash. :slight_smile:

Believe this div should have a zIndex applied:

Unless dropdown is already applied.

Also, I think you arent showing anything:

const iconText = (icon, text, children) => {
    return React.createElement(
        'div',
        null,
        React.createElement(
            'i',
            { class: 'me-1 ' + icon },
        ),
        text,
        children
    );
}

dagcomponentfuncs.DropdownLinks = function (props) {
    return React.createElement(
        'div',
        { className: 'dropdown show' },
        [React.createElement(
            'button',
            {
                className: "me-1 btn btn-primary btn-sm",
            },
            iconText("bi-link-45deg", "Links ", React.createElement(
                'i',
                { className: 'me-1 bi bi-caret-down-fill' },
            )),
        ),
        React.createElement(
            'div',
            { className: "dropdown-menu show" },
            props.value.map(([icon, title, url]) =>
                React.createElement(
                    'a',
                    { href: url },
                    iconText(icon, title)
                )
            )
        )]
    )

};

Try this. I think you didnt have your children correct.

React createElement is laid out like this:
(element type, props, children)

You had (element type, props, child1, child2)

Thanks for your suggestions, but I don’t think either of these are the problem.

The [ ] around the children shouldn’t be necessary, since createElement uses the … operator for children to allow the children to be passed directly:

  • optional ...children: Zero or more child nodes. They can be any React nodes, including React elements, strings, numbers, portals, empty nodes (null, undefined, true, and false), and arrays of React nodes.

See spread syntax in JavaScript docs, also see the example from the createElement docs, where they are not using brackets for multiple children:

import { createElement } from 'react';

function Greeting({ name }) {
  return createElement(
    'h1',
    { className: 'greeting' },
    'Hello ',
    createElement('i', null, name),
    '. Welcome!'
  );
}

Anyway, I have confirmed that the elements are created correctly by my code as is. I can see that they are properly nested in the Chrome element inspector:

<div class="dropdown show"><button class="me-1 btn btn-primary btn-sm">
        <div><i class="me-1 bi-link-45deg"></i>Links <i class="me-1 bi bi-caret-down-fill"></i></div>
    </button>
    <div class="dropdown-menu show"><a
            href="https://...">
            <div><i class="me-1 bi-robot"></i>Autobot</div>
        </a><a
            href="https://...">
            <div><i class="me-1 bi-speedometer"></i>Grafana</div>
        </a><a
            href="https://...">
            <div><i class="me-1 bi-list-columns-reverse"></i>Splunk</div>
        </a><a href="https://...">
            <div><i class="me-1 bi-bell"></i>Alertmanager</div>
        </a><a href="https://...">
            <div><i class="me-1 bi-arrow-repeat"></i>ArgoCD</div>
        </a><a
            href="https://...">
            <div><i class="me-1 bi-diagram-3"></i>Org Details</div>
        </a><a href="https://...">
            <div><i class="me-1 bi bi-search"></i>Glean Search</div>
        </a></div>
</div>

Regarding the zindex, that should automatically be set to 1000 by via the dropdown CSS class. I have confirmed this in element inspector:

This is while I have the dropdown-menu div highlighted. You can see that z-index is set to the --bs-dropdown-zindex variable, and the variable is set to 1000.

Can you provide a minimal example that I can run?

Let me see if I can come up with one.

1 Like

Here’s a minimal example…

dag-dropdown.py:

import dash_ag_grid as dag
from dash import Dash, html, dcc
import dash_bootstrap_components as dbc


social_links = [
    ("bi-linkedin", "LinkedIn", "https://linkedin.com"),
    ("bi-twitter", "Twitter", "https://twitter.com"),
    ("bi-facebook", "Facebook", "https://facebook.com"),
    ("bi-instagram", "Instagram", "https://instagram.com"),
    ("bi-youtube", "YouTube", "https://youtube.com"),
]

dev_links = [
    ("bi-github", "GitHub", "https://github.com"),
    ("bi-gitlab", "GitLab", "https://gitlab.com"),
    ("bi-bitbucket", "Bitbucket", "https://bitbucket.org"),
    ("bi-jira", "Jira", "https://atlassian.com"),
    ("bi-stack-overflow", "Stack Overflow", "https://stackoverflow.com"),
]

data = [
    {"type": "Social Media", "links": social_links},
    {"type": "Development", "links": dev_links}
]

columnDefs = [
    {
        "field": "type",
        "headerName": "Link Type",
    },
    {
        "field": "links",
        "headerName": "Links",
        "cellRenderer": "DropdownLinks",
    },
]


grid = dag.AgGrid(
    id="custom-component-btn-grid",
    columnDefs=columnDefs,
    rowData=data,
    columnSize="autoSize",
    dashGridOptions={"rowHeight": 48},
)


app = Dash(__name__, external_stylesheets=[dbc.themes.SPACELAB])

app.layout = html.Div(
    [
        html.H1("Dash AG Grid + Dropdown Menu"),
        grid
    ]
)

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

dashAgGridComponentFunctions.js:

var dagcomponentfuncs = window.dashAgGridComponentFunctions = window.dashAgGridComponentFunctions || {};

function iconText(icon, text, children) {
    return React.createElement(
        'div',
        null,
        React.createElement(
            'i',
            { class: 'me-1 ' + icon },
        ),
        text,
        children
    );
}

dagcomponentfuncs.DropdownLinks = function (props) {
    return React.createElement(
        'div',
        { className: 'dropdown show' },
        React.createElement(
            'button',
            {
                className: "me-1 btn btn-primary btn-sm",
            },
            iconText("bi-link-45deg", "Links ", React.createElement(
                'i',
                { className: 'me-1 bi bi-caret-down-fill' },
            )),
        ),
        React.createElement(
            'div',
            { className: "dropdown-menu show" },
            props.value.map(([icon, title, url]) =>
                React.createElement(
                    'a',
                    { href: url },
                    iconText(icon, title)
                )
            )
        )
    )

};

The cells are a bit larger on this app (probably because not all the same stylesheets are loaded) and this allowed me to see that the dropdown is rendering but it appears to be clipped within the cell that it’s in (notice the top border under each button):

Is there any CSS style that could be applied to prevent it from being clipped?

Alternatively, I may look at your suggestion to use a cell editor instead since it has a built-in popup feature.

You can pass an overflow: show in the cellStyle for the columnDef, and I think that will bring it out. :slight_smile:

I tried adding it to my exmaple like this:

columnDefs = [
    {
        "field": "type",
        "headerName": "Link Type",
    },
    {
        "field": "links",
        "headerName": "Links",
        "cellRenderer": "DropdownLinks",
        "cellStyle": {"overflow": "show"}
    },
]

But nothing changed. Did I do something wrong?

@jblang

Try running this example. It uses the dbc components, plus some css to make th menu visible:



import dash_ag_grid as dag
from dash import Dash, html
import dash_bootstrap_components as dbc

data = [
    {"type": "Social Media", "links": "link"},
    {"type": "Development", "links": "link"}
]

columnDefs = [
    {
        "field": "type",
        "headerName": "Link Type",
    },
    {
        "field": "links",
        "headerName": "Links",
        "cellRenderer": "DropdownLinks",
        "cellStyle": {"overflow": "visible"}
    },
]


grid = dag.AgGrid(
    id="custom-component-btn-grid",
    columnDefs=columnDefs,
    rowData=data,
    columnSize="autoSize",
    dashGridOptions={"rowHeight": 48}
)


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

app.layout = html.Div(
    [
        html.H1("Dash AG Grid + Dropdown Menu"),
        grid
    ]
)

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


dagcomponentfuncs.DropdownLinks = function (props) {
    return  React.createElement(
        window.dash_bootstrap_components.DropdownMenu,
        {label: "Menu"},
        [
          React.createElement(window.dash_bootstrap_components.DropdownMenuItem, { href: "#/action-1"}, "Item 1"),
          React.createElement(window.dash_bootstrap_components.DropdownMenuItem, { href: "#/action-2" }, "Item 2"),
          React.createElement(window.dash_bootstrap_components.DropdownMenuItem, { href: "#/action-3"}, "Item 3")
        ]
      )
};

------------
Add the following to a .css file in assets folder.  It makes the focused row have a
lower z-index so the menu is displayed when the button is clicked

.ag-row-focus {
    z-index: 999;
}

"""

5 Likes

Thank you SO much. This did the trick!

1 Like

thank you for a very nice example @AnnMarieW

do you know if it’s possible to have a different links list in each row? e.g. the way @jblang had in the example in Use dropdown link menu in Dash AG Grid - #8 by jblang
I want some of the rows to display e.g. 2 links, other rows 3, etc. depending on what is stored in the cell

Hi @kiteme

Here .js file updated to use the data first example:

var dagcomponentfuncs = (window.dashAgGridComponentFunctions = window.dashAgGridComponentFunctions || {});


function iconText(icon, text, children) {
    return React.createElement(
        'div',
        null,
        React.createElement(
            'i',
            { class: 'me-1 ' + icon },
        ),
        text,
        children
    );
}


dagcomponentfuncs.DropdownLinks = function (props) {
    return  React.createElement(
        window.dash_bootstrap_components.DropdownMenu,
        {label: "Menu"},
        props.value.map(([icon, title, url]) =>
                React.createElement(
                    window.dash_bootstrap_components.DropdownMenuItem,
                    { href: url },
                    iconText(icon, title)
                )
            )       
      )
};

See live example here:

1 Like

Also, if you only have a couple links and you don’t want to use any JavaScript, you can use the built-in markdown component. You just need to format your links in markdown syntax


 

import dash_ag_grid as dag
from dash import Dash, html
import dash_bootstrap_components as dbc

social_links = """
 [LinkedIn](https://linkedin.com), 
 [Twitter](https://twitter.com)
"""


dev_links ="""
 [GitHub](https://github.com),
 [Gitlab](https://gitlab.com),
 [Stack Overflow](https://stackoverflow.com),
"""
  
data = [
    {"type": "Social Media", "links": social_links},
    {"type": "Development", "links": dev_links}
]

columnDefs = [
    {
        "field": "type",
        "headerName": "Link Type",
    },
    {
        "field": "links",
        "headerName": "Links",
        "cellRenderer": "markdown",     
        "linkTarget": "_blank"
    },
]


grid = dag.AgGrid(
    id="custom-component-btn-grid",
    columnDefs=columnDefs,
    rowData=data,
    columnSize="autoSize",
    dashGridOptions={"rowHeight": 48}
)


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

app.layout = html.Div(
    [
        html.H1("Dash AG Grid with Markdown"),
        grid
    ]
)

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

1 Like

Thank you very much, exactly what I needed!

Ps. the PyCafe link fails with this error when trying to install packages:
image

no problem as it worked locally without any problem :slight_smile: