Hi everyone I’m trying to build a generic properties-box.
The properties box is an AGGrid with 2 columns (property key and value)
It is defined by a simple dictionary with the name, value and type per variable.
Eg.
properties = [
{'name':'bool', 'value':False, 'type' : 'bool'},
{'name':'int', 'value':0, 'type' : 'int'},
{'name':'float', 'value': 0.0, 'type' : 'float'},
{'name':'range', 'value': None, 'type' : 'range'},
{'name':'text', 'value':'POO', 'type' : 'text'},
{'name':'email', 'value': None, 'type' : 'email'},
{'name':'password', 'value': None, 'type' : 'password'},
{'name':'select', 'value': None, 'type' : 'select',
'args' : {
'options' :
[
{"label": "Option 1", "value": "opt_1"},
{"label": "Option 2", "value": "opt_2"},
{"label": "Option 3", "value": "opt_3", "disabled": True},
{"label": "Option 4", "value": "opt_4"},
{"label": "Option 5", "value": "opt_5"},
],
},
},
{'name':'date', 'value': datetime.datetime.utcnow(), 'type' : 'date'},
{'name':'color', 'value': '#FF0000', 'type' : 'color'},
]
the resulting Aggrid would look something like this.
Almost everything works.
I have issues with
- the ‘select’ dropdown (a bootstrap Select) : onChange() does not trigger
- the date input (a dash mantine DatePicker) : Datepicker doesn’t show correctly
- aligning / sizing the Date and Color inputs to their cell.
Here is the code
python:
import json
import datetime
import dash
from dash import dcc, html, Output, Input, State
import dash_ag_grid as dag
import dash_bootstrap_components as dbc
import dash_mantine_components as dmc
type_components = {
'bool':
{
'library' : 'dash_bootstrap_components',
'name' : 'Checkbox',
'args' : {},
},
'int':
{
'library' : 'dash_bootstrap_components',
'name' : 'Input',
'args' : {
'type' : 'number',
'step' : 1,
'placeholder' : 'enter integer...',
},
},
'float':
{
'library' : 'dash_bootstrap_components',
'name' : 'Input',
'args' : {
'type' : 'number',
'step' : 0.001,
'placeholder' : 'enter float...',
},
},
'range':
{
'library' : 'dash_bootstrap_components',
'name' : 'Input',
'args' : {
'type' : 'range',
},
},
'text':
{
'library' : 'dash_bootstrap_components',
'name' : 'Input',
'args' : {
'type' : 'text',
'placeholder' : 'enter text...',
},
},
'email':
{
'library' : 'dash_bootstrap_components',
'name' : 'Input',
'args' : {
'type' : 'email',
'placeholder' : 'enter email...',
},
},
'password':
{
'library' : 'dash_bootstrap_components',
'name' : 'Input',
'args' : {
'type' : 'password',
'placeholder' : 'enter password...',
},
},
'select':
{
'library' : 'dash_bootstrap_components',
'name' : 'Select',
'args' : {
'placeholder' : 'select option...',
},
},
'date':
{
'library' : 'dash_mantine_components',
'name' : 'DatePicker',
'args' : {
'placeholder' : 'select date...',
},
},
'color':
{
'library' : 'dash_bootstrap_components',
'name' : 'Input',
'args' : {
'type' : 'color',
},
},
}
def dash_aggrid_properties_box(id =None, properties = None):
if not properties:
return
box_properties = []
for prop in properties:
box_prop = dict(prop, **{'component' : type_components.get(prop['type'])})
if 'args' in box_prop:
box_prop['component']['args'] = dict(box_prop['component']['args'], **box_prop.pop('args') )
box_properties.append(box_prop)
return dag.AgGrid(
id=id,
columnDefs=[
{'field' : 'name', 'minWidth' : 50,'suppressSizeToFit':True},
{'field' : 'value', 'cellRenderer' : 'componentRenderer'},
{'field' : 'type', 'hide':True},
{'field' : 'component', 'hide':True},
],
rowData=box_properties,
columnSize="responsiveSizeToFit",
defaultColDef={
'display' : 'flex',
'flex-flow': 'row nowrap',
"suppressMovable" : True,
'resizable':True,
'minWidth' : 200,
"sortable": False,
"filter": False,
} ,
dashGridOptions={
"rowHeight": 50, # to check correct filling of the cell
},
style = {
'flex' : '1 0 0',
}
)
if __name__=='__main__':
test_properties = [
{'name':'bool', 'value':False, 'type' : 'bool'},
{'name':'int', 'value':0, 'type' : 'int'},
{'name':'float', 'value': 0.0, 'type' : 'float',
'args' : {
'min' : -10.0,
'max' : 10.0,
'step' : 0.1,
'placeholder' : 'enter 1 decimal float between -10 <-> 10...',
},
},
{'name':'range', 'value': None, 'type' : 'range'},
{'name':'text', 'value':'POO', 'type' : 'text'},
{'name':'email', 'value': None, 'type' : 'email'},
{'name':'password', 'value': None, 'type' : 'password'},
{'name':'select', 'value': None, 'type' : 'select',
'args' : {
'options' :
[
{"label": "Option 1", "value": "opt_1"},
{"label": "Option 2", "value": "opt_2"},
{"label": "Option 3", "value": "opt_3", "disabled": True},
{"label": "Option 4", "value": "opt_4"},
{"label": "Option 5", "value": "opt_5"},
],
},
},
{'name':'date', 'value': datetime.datetime.utcnow(), 'type' : 'date'},
{'name':'color', 'value': '#FF0000', 'type' : 'color'},
]
app = dash.Dash(__name__)
app.layout = html.Div( [
html.Div(
'',
id='output-text',
style = {
'flex' : '0 0 1',
}
),
dash_aggrid_properties_box(
id ='properties-box',
properties = test_properties
),
],
style = {
'display' : 'flex',
'flex-flow': 'column wrap',
'height': '100vh',
}
)
@app.callback(
Output("output-text", "children"),
Input("properties-box", "cellValueChanged"),
)
def selected(changed):
return f"\nYou have selected {json.dumps(changed, indent=4)}\n"
app.run_server(debug=True)
and the javascript with a dynamic componentRenderer in the dagcomponentfunc’s (assets folder)
var dagcomponentfuncs = (window.dashAgGridComponentFunctions = window.dashAgGridComponentFunctions || {});
dagcomponentfuncs.componentRenderer = function (props) {
const { data } = props;
// on click
function onClick() {
if (event == undefined) return;
var value;
// type specs
if (props.data.type == 'bool') {
value = event.target.checked;
// exception for checkbox trigger callback
var colId = props.column.colId;
props.node.setDataValue(colId, value);
this.value = value;
} else if (props.data.type == 'select') {
if (event.target.selectedIndex > 0) {
value = event.target.selectedOptions[0].text;
};
} else {
value = event.target.value;
};
};
// on change
function onChange() {
var value = event.target.value;
if (value === '') return;
if (!event.target.validity || !event.target.validity.valid) return;
// type specs
if (props.data.type == 'int') {
value = parseInt(value);
} else if (props.data.type == 'float') {
value = parseFloat(value);
};
this.value = value;
// trigger callback
const colId = props.column.colId;
props.node.setDataValue(colId, value);
};
// init component
var component_func = window[data.component.library][data.component.name];
var component_args = {
...data.component.args,
value: props.value,
onChange: onChange,
style: {
cursor: 'pointer',
flex: '1 1 0',
border: '1px solid #0000FF', // to check correct filling of the cell
}
};
//// exception for check box
if (data.type == 'bool') component_args['checked'] = component_args.value; // don't know a direct(python-like) way to pop the value from an object
//// build component
component = React.createElement(
'div',
{
onClick: onClick,
style: {
height: props.eGridCell.scrollHeight,
flex: '1 1 0',
display: 'flex',
},
},
[
React.createElement(
component_func,
component_args
)
]
);
return component;
};
Could someone help me out please.
Thx in advance
PS:
I also tried to do something similar with Editors but this failed miserably…