Creating dynamic dropdowns using dash clientside callbacks

hi all, this is the third web app i’m building using dash. I want to design a left side-bar with two dropdowns with minimal spacing between the top and bottom dropdowns. i succesfully did that but i want to design it in way that when the top dropdown is opened, the the one at the bottom is not covered but moves directly below it. After the top dropdown option is selected, the dropdown below it moves back to its original position. Is it possible to do that using clientside-callbacks or is there another way?

Good question, @Flexxie .

@Amazigh have you done something like this before?

Not sure if this is exactly what you are referring to but I used dmc.Drawer and dmc.Accordion to get my navigation looking pretty:

dmc.Drawer(
            title="Drawer Example",
            id="drawer-simple",
            padding="md",
            zIndex=1000,
            children=[
                dmc.Box(
                    children=[
                        dmc.Image(
                            radius="md",
                            src="assets/world_animated.gif",
                            fit="contain",
                            style={
                                "width": "60%",
                                "height": "60%",
                                "objectFit": "contain",
                                "position": "absolute",
                                "top": "50%",
                                "left": "50%",
                                "transform": "translate(-50%, -50%)"
                            }
                        ),
                        dmc.Image(
                            radius="md",
                            src="assets/coatofarms.png",
                            fit="contain",
                            style={
                                "position": "absolute",
                                "top": 0,
                                "left": 0,
                                "width": "100%",
                                "height": "100%",
                                "objectFit": "contain",
                                "zIndex": 1
                            }
                        )
                    ],
                    style={
                        "position": "relative",
                        "width": "100%",
                        "height": "150px",  # Adjust this value as needed
                        "overflow": "hidden"  # This ensures the transformed image doesn't overflow
                    }
                ),
                dmc.Divider(label="Navigation", color='orange'),
                dmc.Stack([
                    dmc.Group([
                        dmc.Accordion(
                            value=["flexibility"],
                            multiple=True,
                            children=[
                                dmc.AccordionItem(
                                    [
                                        dmc.AccordionControl("Cartography", style={"fontSize": 24}, icon=DashIconify(icon="noto:world-map", width=30),),
                                        dmc.AccordionPanel(
                                            dmc.Stack([
                                                dmc.Group([
                                                        dmc.Space(w=20),
                                                        DashIconify(icon="noto:globe-showing-americas", width=30),
                                                        dmc.Anchor(
                                                            "Aransas Map",
                                                            href="/",
                                                            style={'color': 'white', "fontSize": 24}
                                                        )
                                                    ]),
                                            ])
                                        ),
                                    ],
                                    value="cartography",
                                ),
                                dmc.AccordionItem(
                                    [
                                        dmc.AccordionControl("Freeform", style={"fontSize": 24}, icon=DashIconify(icon="vscode-icons:file-type-libreoffice-draw", width=30),),
                                        dmc.AccordionPanel(
                                            dmc.Stack([
                                                dmc.Group([
                                                        dmc.Space(w=20),
                                                        DashIconify(icon="noto-v1:man-artist-light-skin-tone", width=30),
                                                        dmc.Anchor(
                                                            "Create Notes",
                                                            href="/freeform",
                                                            style={'color': 'white', "fontSize": 24}
                                                        )
                                                    ]),

                                                dmc.Group([
                                                        dmc.Space(w=20),
                                                        dmc.Tooltip(
                                                            label="🏗️ Under Construction 🚧",
                                                            position="right",
                                                            offset=10,
                                                            children=[
                                                                DashIconify(icon="logos:teamgrid", width=30),
                                                            ],
                                                            zIndex=1005,
                                                            withArrow=True,
                                                            transitionProps={
                                                                "transition": "scale-y",
                                                                "duration": 200,
                                                                "timingFunction": "ease",
                                                            },
                                                        ),

                                                        dmc.Skeleton(h=24, w=150,),
                                                        # dmc.Anchor(
                                                        #     "Publications",
                                                        #     href="/freeform/publications",
                                                        #     style={'color': 'white', "fontSize": 24}
                                                        # )
                                                    ]),


                                            ]),
                                        ),
                                    ],
                                    value="freeform",
                                ),
                                dmc.AccordionItem(
                                    [
                                        dmc.AccordionControl("360 Panorama", style={"fontSize": 24}, icon=DashIconify(icon="flat-color-icons:panorama", width=30),),
                                        dmc.AccordionPanel(
                                            dmc.Stack([
                                                dmc.Group([
                                                    dmc.Space(w=20),
                                                    DashIconify(icon="fxemoji:videogame", width=30),
                                                    dmc.Anchor(
                                                        "Geo Guesser Game",
                                                        href="/geo_game_select",
                                                        style={'color': 'white', "fontSize": 24}
                                                    )], style={ }),


                                            ]),
                                        ),
                                    ],
                                    value="panorama",
                                ),
                            ],
                        )
                    ], grow=True),



                    # dmc.Group([
                    #
                    #     dmc.Image(
                    #         radius="md",
                    #         h=30,
                    #         w=30,
                    #         src="assets/sensor.png",
                    #     ),
                    #     dmc.Anchor(
                    #         "Sensor Management",
                    #         href="https://community.plotly.com/t/dash-mantine-components/58414",
                    #         style={'color': 'white', "fontSize": 24},
                    #     )]),
                    dmc.Divider(label="Sister Applications", color='orange'),
                    dmc.Group([
                        DashIconify(icon="skill-icons:django", width=30),
                        dmc.Anchor(
                            "Django",
                            href="https://geomapindex.com/",
                            style={'color': 'white', "fontSize": 24},
                        )]),
                    dmc.Group([
                        DashIconify(icon="logos:amazon-connect", width=30),
                        dmc.Anchor(
                            "API",
                            href="https://geomapindex.com/api/docs",
                            style={'color': 'white', "fontSize": 24},
                        )]),
                    dmc.Group([
                        DashIconify(icon="flat-color-icons:shop", width=30),
                        dmc.Anchor(
                            "Shop",
                            href="https://pipinstallpython.pythonanywhere.com/catalogue/",
                            style={'color': 'white', "fontSize": 24,

                                   },
                        )], ),
                    dmc.Divider(label="Resources", color='orange'),
                    dmc.Group([
                        DashIconify(icon="vscode-icons:folder-type-docs-opened", width=30),
                        dmc.Anchor(
                            "Docs",
                            href="https://pip-install-python.com/",
                            style={'color': 'white', "fontSize": 24},
                        )]),
                    dmc.Group([
                        DashIconify(icon="openmoji:youtube", width=30),
                        dmc.Anchor(
                            "Youtube",
                            href="https://www.youtube.com/@pipinstallpython",
                            style={'color': 'white', "fontSize": 24},
                        )]),
                    dmc.Group([
                        DashIconify(icon="openmoji:github", width=30),
                        dmc.Anchor(
                            "Github",
                            href="https://github.com/pip-install-python",
                            style={'color': 'white', "fontSize": 24},
                        )]),
                ], align='stretch', justify='flex-start')
            ]
        ),

Still need to route a callback and a button to open and close the dmc.Drawer

Below are two approaches for achieving this functionality.

Approach 1: Custom JavaScript Event Listeners

In this approach, we directly manipulate the style of the dropdown based on focus and blur events using custom JavaScript. This method is straightforward and gives you precise control over the dropdown behavior.

from dash import Dash, dcc, html, Input, Output, _dash_renderer
import dash_mantine_components as dmc
from dash_iconify import DashIconify


_dash_renderer._set_react_version('18.2.0')

app = Dash(__name__)


# Define layout
app.layout = dmc.MantineProvider(
    children=[
        dmc.Box(
            children=[
                
                dmc.Select(
                    label="Your favorite library",
                    data=["Pandas", "NumPy", "TensorFlow", "PyTorch", "Python", "Java", "G0", "Dash", "Plolty"],
                    value='Pandas',
                    placeholder="Pick values",
                    id='select',
                    w=400,
                 
                    maxDropdownHeight = 200,
                ),
                  dmc.Select(
                    label="Your favorite library",
                    data=["Pandas", "NumPy", "TensorFlow", "PyTorch"],
                    value='Pandas',
                    placeholder="Pick values",
                    id='select-2',
                    w=400,
           
                ),
            ]
        )
    ]
)

# Custom JavaScript to detect focus and open state
app.clientside_callback(
    """
    function(value) {
    const no_update = window.dash_clientside.no_update
        var selectInput = document.querySelector("#select");
        if (selectInput) {
            selectInput.addEventListener("focus", () => {
                console.log("Dropdown focused");
                
                document.getElementById("select").style.marginBottom = "220px";
            });
            selectInput.addEventListener("blur", () => {
                console.log("Dropdown unfocused");
                document.getElementById("select").style.marginBottom = "0px";
            });
        }
        return no_update;
    }
    """,
    Output("select", "children"),
    Input("select", "value")
)

# Run app
if __name__ == "__main__":
    app.run_server(debug=True)

Approach 2: Using dropdownOpened Property

Alternatively, the Dash Mantine Components library provides a dropdownOpened property that can be used to detect whether the dropdown is open. However, I’ve encountered a bug in the library that prevents this property from functioning correctly. As a result, this approach may not work as expected at the moment.
@AnnMarieW is this how the property (dropdownOpened) should be used?

from dash import Dash, dcc, html, Input, Output, _dash_renderer
import dash_mantine_components as dmc
from dash_iconify import DashIconify

# Set React version
_dash_renderer._set_react_version('18.2.0')


app = Dash(__name__)

# Define layout
app.layout = dmc.MantineProvider(
    children=[
        dmc.Box(
            children=[
                dmc.Select(
                    label="Your favorite library",
                    data=["Pandas", "NumPy", "TensorFlow", "PyTorch"],
                    value='Pandas',
                    placeholder="Pick values",
                    id='select',
                    w=400,
                ),
                dmc.Select(
                    label="Another Select",
                    data=["Pandas", "NumPy", "TensorFlow", "PyTorch"],
                    value='Pandas',
                    placeholder="Pick values",
                    id='select-2',
                    w=400,
                ),
            ]
        )
    ]
)

# Attempt to use dropdownOpened property (currently not functional due to a bug)
app.clientside_callback(
    """
    function(isDropdownOpen, value) {
        console.log("Dropdown Open:", isDropdownOpen); // Log dropdown focus state
        return isDropdownOpen ? [{"marginBottom": "300px"}] : [{"marginBottom": "0px"}];
    }
    """,
    Output("select", "style"),
    Input("select", "dropdownOpened"),
    Input("select", "value"),
)


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


1 Like

Hi @Amazigh
The dropdownOpened can be used to manually open or close the dropdown in a callback, but it currently is not updated based on user interaction.

@Flexxie
It is not typical for a dropdown to move other content on the page. While it’s possible to make that happen it adds additional complexity to the app.

For that type it functionality, it would be better to use something like a dmc.Accordion as @PipInstallPython suggested.

1 Like

Hi @Flexxie ,

What about NavLink component in dash mantine components https://www.dash-mantine-components.com/components/navlink? If you pass children to it it will have the exact functionality as you described. I use it in navbars:

collapsed:

uncollapsed: