Dash AG Grid: inline dropdown that allows new value

I have an AG Grid with a dropdown control.

I’m following the pattern of using dashAgGridFunctions.js (like below) and the cellEditorParams property of the column.

This basically works, but I want the user to be able to enter a value that is not in the dropdown as well.

So the idea is, the dropdown will show the most common values for the given column from database results, but the user can also enter any text they want.

Is this possible/straightforward?

I haven’t written any javascript in quite a while, so I’m hoping this is a common pattern that already exists.

Thanks in advance.>

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

dagfuncs.dynamicTrueFalseOptions = function(params) {
return {
values: [‘True’, ‘False’],
};
}

dagfuncs.DCC_Dropdown = class {

// gets called once before the renderer is used

init(params) {
// create the cell
this.params = params;
this.ref = React.createRef();

// function for when Dash is trying to send props back to the component / server
var setProps = (props) => {
    if (props.value) {
        // updates the value of the editor
        this.value = props.value;

        // re-enables keyboard event
        delete params.colDef.suppressKeyboardEvent

        // tells the grid to stop editing the cell
        params.api.stopEditing();

        // sets focus back to the grid's previously active cell
        this.prevFocus.focus();
    }
}
this.eInput = document.createElement('div')

// renders component into the editor element
ReactDOM.render(React.createElement(window.dash_core_components.Dropdown, {
    options: params.values, value: params.value, ref: this.ref, setProps, style: {width: params.column.actualWidth},
}), this.eInput)

// allows focus event
this.eInput.tabIndex = "0"

// sets editor value to the value from the cell
this.value = params.value;

}

// gets called once when grid ready to insert the element
getGui() {
return this.eInput;
}

focusChild() {
// enter keyboard event
const keyboardEvent = new KeyboardEvent(‘keydown’, {
code: ‘Enter’,
key: ‘Enter’,
charCode: 13,
keyCode: 13,
view: window,
bubbles: true
});

// needed to delay and allow the component to render
setTimeout(() => {
    var inp = this.eInput.getElementsByClassName('Select-control')[0]
    inp.tabIndex = '1'
    inp.focus()

    // disables keyboard event
    this.params.colDef.suppressKeyboardEvent = (params) => {
           const gridShouldDoNothing = params.editing
           return gridShouldDoNothing;
       }
    // shows dropdown options
    inp.dispatchEvent(keyboardEvent)
}, 100)

}

// focus and select can be done after the gui is attached
afterGuiAttached() {
// stores the active cell
this.prevFocus = document.activeElement

// adds event listener to trigger event to go into dash component
this.eInput.addEventListener('focus', this.focusChild())

// triggers focus event
this.eInput.focus();

}

// returns the new value after editing
getValue() {
return this.value;
}

// any cleanup we need to be done here
destroy() {
// sets focus back to the grid’s previously active cell
this.prevFocus.focus();
}
}

Hi @zack_nc

It’s not possible for users to add new options with the dcc.Dropdown component. However, this feature is available in dmc.Select - see the"createable" example on this page:

You can see some examples of using the dmc.Select component in the preliminary Dash AG Grid docs (this has not yet made it’s way into the official Dash docs yet)

dag-docs

2 Likes

Thanks!

I have a head’s up and a question:

The head’s up is that I wasn’t able to make this work until I tweaked the javascript for the DCC_Dropdown like this:

ReactDOM.render(
React.createElement(window.dash_mantine_components.Select, {
data: params[0].options,
value: params.value,

Notice that I added the ‘[0]’ because when I look in the debugger, the options aren’t on the params, but params[0].

Once I made that change, it mostly works, but, the “add new” functionality isn’t working for me like in the demo.

If I enter a value that isn’t in the list, I don’t get the option to add it.

I’m setting up the column like this:

elif i == ‘Category’:
# entry[‘editable’] = True
# entry[‘cellRenderer’] = ‘Dropdown’
entry[‘cellEditor’] = {“function”: “DMC_Select”}
entry[‘cellEditorParams’] = {
“options”: dfCategoryOptions.category.unique(),
“clearable”: False,
“creatable”: True,
“shadow”: “xl”,
},
entry[‘cellEditorPopup’] = True,
entry[‘singleClickEdit’] = True

I’m wondering if this could be a matter of versions or something?

I’m using dash_ag_grid 2.3.0 and dash-mantine-components 0.12.1

Hello @zack_nc,

Could you please create a full MRE for this?

I use the select all the time, and I dont see this issue. The params should not be a list.

Sure.

The python code is below.

My dashAgGridFunctions.js is copied straight from the example at Dash

But like I said, it didn’t work until I fixed the params.options reference.

import pandas as pd

import dash
from dash import Input, Output, html
import dash_bootstrap_components as dbc
import dash_mantine_components as dmc

import dash_ag_grid as dag

dash.register_page(__name__, name='dashboardDemo', group='MISO')

@dash.callback(
    Output("div-topology-projects", "children"),
    Input("div-topology-projects", "id")
    
)
def populateTopologyProjects(id):

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

    return generateTable(df, 'table-topology-projects', tableHeight=300)

def generateTable(table, tableid, fixedColumnIndex=0, tableHeight=None, doFilter=False):
    
    columnTypes = {
        "numberColumn": {"width": 130, "filter": "agNumberColumnFilter"},
        "medalColumn": {"width": 100, "columnGroupShow": "open", "filter": False},
        "nonEditableColumn": {"editable": False},
        "editableColumn": {"editable": True}
    }
    
    columns = []
    columnIndex = 1
    for i in table.columns:
        # if table[i].dtype == 'float64':
        entry = {'headerName': i, 'field': i}
        entry['minWidth'] = 110
        entry['type'] = 'editableColumn'

        if i == 'country':
            # entry['editable'] = True
            # entry['cellRenderer'] = 'Dropdown'
            entry['cellEditor'] = {"function": "DMC_Select"}
            entry['cellEditorParams'] =  {
                "options": table.country.unique(),
                "clearable": False,
                "creatable": True,
                "shadow": "xl"
            },
            entry['cellEditorPopup'] = True
            entry['singleClickEdit'] = True
        
        if columnIndex <= fixedColumnIndex:
            entry['pinned'] = 'left'
        columnIndex += 1

        columns.append(entry)
    table = table.to_dict('records')

    defaultColDef = {
        "resizable": True,
        "sortable": True,
        "editable": False,
        "tooltipComponent": "CustomTooltip"
        }
    
    dashGridOptions = {
        "undoRedoCellEditing": True, 
        "rowSelection": "single", 
        "columnTypes": columnTypes,
        "tooltipShowDelay": 400
        }
    
    if tableHeight is None:
        style={'height': '100%', 'width': '100%'}
        dashGridOptions['domLayout'] = 'autoHeight'
    else:
        style={'height': int(f'{tableHeight}'), 'width': '100%'}
        if (len(table) * 30) < int(tableHeight):
            dashGridOptions['domLayout'] = 'autoHeight'

    # Dash tables are very slow when using fixed rows or columns, so only do that when necessary
    return dag.AgGrid(
        id=tableid,
        columnDefs=columns,
        rowData=table,
        columnSize="sizeToFit",
        defaultColDef=defaultColDef,
        dashGridOptions=dashGridOptions,
        className="ag-theme-balham",
        style=style
    )
    
layout =  dbc.Container(fluid=True, children=[
    # Toolbar
    html.Br(),
    dbc.Row([
        dbc.Col([
            html.H4('', id='div-topology-projects-Title'),
            html.Div(
                    id='div-topology-projects'
                )
            ], width=12)
    ]),
])

debug1
debug2
debug3

You have an hanging comma here, thus creating a tuple. :wink:

1 Like

Oh my goodness…

That is diabolical.

Thank you so much for humoring me and pointing that out.

2 Likes

Quick followup (since you’ve been SO helpful):

If I have a very small table/grid, the options get cutoff, like in this screenshot.

Do you know of a property that can be set to make this a little cleaner?

You’d probably have to go back to the default select that the grid offers.

You can give it a shot and see what happens. XD

2 Likes

Maybe have a minimum height for the grid - to make room for the options?

2 Likes

Thanks.

Yet another followup here:

I need a way for the user to select a row, so that I can take data from that row and populate other objects on the screen.

The code below accomplishes this. However, it hijacks the behavior of the inline DMC_select controls.

I was hoping that if I raise PreventUpdate when the selection is coming from a column with a select control, I could prevent this, but that isn’t sufficient. I always get an empty list of options.

Do you know of any way to make it possible to select a row - or a column on a row - without breaking the select control behavior?

@dash.callback(
[
(some outputs),
[Input(‘td-table-topology-projects’, ‘cellClicked’),
Input(‘td-table-topology-projects’, ‘selectedRows’)],
prevent_initial_call=True
)
def projectRowSelected(activeCell, selectedRows):
if activeCell is None:
raise PreventUpdate

if activeCell['colId'] == 'State' or activeCell['colId'] == 'Category':
    raise PreventUpdate

# else: do some stuff

Id make it so that clicking a row doesn’t make the selection, but only when they click a checkbox.

Check the docs for the selection checkbox in the first column, and suppressing row click selection.