Skip to content

Commit db66f29

Browse files
committed
fix for dropdown not opening/displaying in static position. fix for callbacks not executing due to html tag id faulty with uuid4 string (but random string of a-z/0-9 works?)
1 parent 4d7a3f2 commit db66f29

File tree

11 files changed

+173
-55
lines changed

11 files changed

+173
-55
lines changed

dash_entrypoints/assets/__init__.py

Whitespace-only changes.
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.dropdown.dropdown { position: static; }

dash_entrypoints/elements/datatable.py

+25-19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from datetime import datetime
2+
from pathlib import Path
23

4+
import dash_bootstrap_components as dbc
35
import dash_html_components as html
46
import dash_table
57
import numpy as np
@@ -39,12 +41,12 @@ def make_dates_list(years=None, months=np.arange(1, 13, 1), days=np.arange(1, 32
3941

4042

4143
def add_table(
42-
table_name=None,
43-
df=None,
44-
dropdown_options=None,
45-
dropdown_columns=[],
46-
table_expandable=True,
47-
table_width=90,
44+
table_name: str = None,
45+
df: pd.DataFrame = None,
46+
dropdown_options: dict = None,
47+
dropdown_columns: list = None,
48+
table_expandable: bool = True,
49+
table_width: int = 90,
4850
**kwargs,
4951
):
5052
"""Dash DataTable wrapper to provide dropdown columns along with free field columns. Tables are row extendable.
@@ -87,6 +89,7 @@ def add_table(
8789
]
8890

8991
table_title = str(table_name).replace("_", " ").capitalize()
92+
table_button_name = "--".join([__name__, table_name]).replace(".", "--")
9093

9194
layout = html.Div(
9295
[
@@ -102,26 +105,29 @@ def add_table(
102105
dropdown=dropdown_options,
103106
),
104107
html.Div(id=f"{table_name}-container"),
105-
html.Button("Add Row", id=f"{table_name}-button", n_clicks=0)
108+
html.Button("Add Row", id=table_button_name, n_clicks=0)
106109
if table_expandable
107-
else html.Div(),
108-
html.Br(),
110+
else html.Div(hidden=True),
109111
],
110112
style={
111113
"padding": "10px",
112114
"width": "90%" if table_width is None else f"{table_width}%",
113115
},
114116
)
115117

116-
@callback(
117-
Output(table_name, "data"),
118-
Input(f"{table_name}-button", "n_clicks"),
119-
State(table_name, "data"),
120-
State(table_name, "columns"),
121-
)
122-
def add_row(n_clicks, rows, columns):
123-
if n_clicks > 0:
124-
rows.append({c["id"]: "" for c in columns})
125-
return rows
118+
if table_expandable:
119+
120+
@callback(
121+
Output(table_name, "data"),
122+
[
123+
Input(table_button_name, "n_clicks"),
124+
State(table_name, "data"),
125+
State(table_name, "columns"),
126+
],
127+
)
128+
def add_row(n_clicks, rows, columns):
129+
if n_clicks > 0:
130+
rows.append({c["id"]: "" for c in columns})
131+
return rows
126132

127133
return layout

dash_entrypoints/misc.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import json
12
import socket
23
from uuid import uuid4
34

5+
import dash
6+
47

58
def get_local_ip_address():
69
"""https://stackoverflow.com/questions/166506/finding-local-ip-addresses-using-pythons-stdlib"""
@@ -16,5 +19,14 @@ def get_local_ip_address():
1619
return ip
1720

1821

19-
def add_random_id(input_string=None):
20-
return "__".join([input_string, str(uuid4())])
22+
def get_callback_context():
23+
context = dash.callback_context
24+
ctx = json.dumps(
25+
{
26+
"states": context.states,
27+
"triggered": context.triggered,
28+
"inputs": context.inputs,
29+
},
30+
indent=2,
31+
)
32+
return ctx
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from dash_entrypoints import assets
2+
3+
DEFAULT_APP_NAME = "InterfaceEntrypoint"
4+
DEFAULT_VIEWS_MODULE = "dash_entrypoints.views"
5+
DEFAULT_ASSETS_FOLDER = assets.__path__[0]

dash_entrypoints/multi_page/cli.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import sys
44

55
from dash_entrypoints.misc import get_local_ip_address
6+
from dash_entrypoints.multi_page import DEFAULT_APP_NAME
7+
from dash_entrypoints.multi_page import DEFAULT_ASSETS_FOLDER
8+
from dash_entrypoints.multi_page import DEFAULT_VIEWS_MODULE
69
from dash_entrypoints.multi_page.entrypoint import run_entrypoint
710

811

@@ -14,7 +17,7 @@ def make_parser():
1417
"--app-name",
1518
type=str,
1619
dest="app_name",
17-
default="App name",
20+
default=DEFAULT_APP_NAME,
1821
help="App name",
1922
)
2023
parser.add_argument(
@@ -30,9 +33,17 @@ def make_parser():
3033
"--views-module",
3134
type=str,
3235
dest="views_module",
33-
default="dash_entrypoints.views",
36+
default=DEFAULT_VIEWS_MODULE,
3437
help="Module import statement for module that contains pages, e.g. 'mypackage.page_subpackage' ",
3538
)
39+
parser.add_argument(
40+
"-a",
41+
"--assets-folder",
42+
type=str,
43+
dest="assets_folder",
44+
default=DEFAULT_ASSETS_FOLDER,
45+
help="Folder with stylesheet, script, image assets",
46+
)
3647
parser.add_argument(
3748
"--port",
3849
"-p",

dash_entrypoints/multi_page/entrypoint.py

+15-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import argparse
21
import importlib
3-
import socket
42
from pathlib import Path
53

64
import dash
@@ -13,9 +11,9 @@
1311
from dash import State
1412

1513
from dash_entrypoints.misc import get_local_ip_address
16-
17-
DEFAULT_APP_NAME = "InterfaceEntrypoint"
18-
DEFAULT_VIEWS_MODULE = "dash_entrypoints.views"
14+
from dash_entrypoints.multi_page import DEFAULT_APP_NAME
15+
from dash_entrypoints.multi_page import DEFAULT_ASSETS_FOLDER
16+
from dash_entrypoints.multi_page import DEFAULT_VIEWS_MODULE
1917

2018

2119
def import_layout_function(layout_file=None, views_imported=None):
@@ -86,6 +84,7 @@ def empty_layout():
8684
def get_app(
8785
app_name=DEFAULT_APP_NAME,
8886
views_module=None,
87+
assets_folder=None,
8988
):
9089
"""
9190
@@ -94,10 +93,15 @@ def get_app(
9493
:param kwargs:
9594
:return:
9695
"""
96+
local_stylesheets = sorted(Path(assets_folder).glob("*.css"))
97+
local_scripts = sorted(Path(assets_folder).glob("*.js"))
98+
9799
app = dash.Dash(
98100
name=app_name,
99101
plugins=[dl.plugins.pages],
100-
external_stylesheets=[dbc.themes.BOOTSTRAP],
102+
assets_folder=assets_folder,
103+
external_stylesheets=local_stylesheets + [dbc.themes.BOOTSTRAP],
104+
external_scripts=local_scripts,
101105
)
102106
register_page_layouts(
103107
app_name=app_name,
@@ -179,6 +183,7 @@ def run_entrypoint(
179183
app_name=DEFAULT_APP_NAME,
180184
ip_address=get_local_ip_address(),
181185
views_module=DEFAULT_VIEWS_MODULE,
186+
assets_folder=DEFAULT_ASSETS_FOLDER,
182187
port=9050,
183188
debug=False,
184189
**kwargs,
@@ -188,12 +193,15 @@ def run_entrypoint(
188193
:param app_name:
189194
:param ip_address:
190195
:param views_module:
196+
:param assets_folder:
191197
:param port:
192198
:param debug:
193199
:param kwargs:
194200
:return:
195201
"""
196-
app = get_app(app_name=app_name, views_module=views_module)
202+
app = get_app(
203+
app_name=app_name, views_module=views_module, assets_folder=assets_folder
204+
)
197205
app = add_base_layout(
198206
app=app, app_name=app_name, ip_address=ip_address, port=port, **kwargs
199207
)

dash_entrypoints/views/add__example_add_table.py

+16-17
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from dash_entrypoints.elements.datatable import add_table
1414
from dash_entrypoints.elements.datatable import make_dates_list
1515
from dash_entrypoints.elements.datatable import make_times_list_24h
16-
from dash_entrypoints.misc import add_random_id
1716

1817

1918
def layout():
@@ -32,27 +31,29 @@ def layout():
3231
"procedure_time_start": pd.to_datetime(dt).round("5min").strftime("%H:%M"),
3332
}
3433
)
35-
dropdown_options_surgery = {
34+
dropdown_options_procedures = {
3635
"subject_id": subject_ids,
3736
"procedure_date": dates,
3837
"procedure_time_start": times,
3938
"procedure_time_end": times,
4039
}
4140

4241
# LAYOUT
43-
submit_btn = add_random_id("submit_btn")
44-
dummy_div = add_random_id("hidden_div")
42+
from uuid import uuid4
4543

44+
submit_btn = "submit--23r98y98feondlkn1oi"
45+
dummy_div = "dummy--as245923ur923hr913"
46+
47+
table_layout = add_table(
48+
table_name=table_1,
49+
df=df_for_table,
50+
dropdown_options=dropdown_options_procedures,
51+
table_expandable=False,
52+
)
4653
layout = html.Div(
4754
[
48-
add_table(
49-
table_name=table_1,
50-
df=df_for_table,
51-
dropdown_options=dropdown_options_surgery,
52-
table_expandable=False,
53-
),
55+
table_layout,
5456
# NOTE: add other tables here
55-
html.Hr(),
5657
html.Button("Submit!", id=submit_btn),
5758
html.Div(id=dummy_div, hidden=True),
5859
html.Br(),
@@ -62,13 +63,11 @@ def layout():
6263
table_names = [
6364
table_1,
6465
]
65-
state_inputs = [Input(submit_btn, "n_clicks")] + [
66-
State(t, "data") for t in table_names
67-
]
66+
state_inputs = [State(t, "data") for t in table_names]
6867

6968
@callback(
70-
[Output(dummy_div, "hidden")],
71-
state_inputs,
69+
Output(dummy_div, "hidden"),
70+
[Input(submit_btn, "n_clicks")] + state_inputs,
7271
)
7372
def save_entry(n_clicks, *args):
7473
if n_clicks is None:
@@ -86,6 +85,6 @@ def save_entry(n_clicks, *args):
8685
f.write(yaml.dump(input_data))
8786
print(f"\nWritten to: {save_path}\n")
8887

89-
return [True]
88+
return # [True]
9089

9190
return layout
+21-8
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,34 @@
1-
import argparse
2-
import importlib
3-
import socket
41
from pathlib import Path
52

6-
import dash
7-
import dash_labs as dl
83
import numpy as np
94
from dash import callback
10-
from dash import Dash
11-
from dash import dcc
125
from dash import html
136
from dash import Input
147
from dash import Output
158
from dash import State
9+
from dash.exceptions import PreventUpdate
10+
11+
from dash_entrypoints.misc import get_callback_context
1612

1713

1814
def layout():
1915
"""Example page layout. Shows a random number as proof of reload on every browser page refresh."""
20-
layout = html.Div([html.H1(f"{__name__}, {np.random.randint(100)}")])
16+
17+
id_hidden_div = "--".join([__name__, "hidden-div"]).replace(".", "")
18+
id_button = "--".join([__name__, "submit-button"]).replace(".", "")
19+
layout = html.Div(
20+
[
21+
html.H1(f"{__name__}, {np.random.randint(100)}"),
22+
html.Button("X!", id=id_button),
23+
html.Div(id=id_hidden_div, hidden=True),
24+
]
25+
)
26+
27+
@callback(Output(id_hidden_div, "hidden"), Input(id_button, "n_clicks"))
28+
def x(n_clicks, *args):
29+
if n_clicks is None:
30+
raise PreventUpdate
31+
print("CALLBACK:", {"n_clicks": n_clicks}, get_callback_context())
32+
return
33+
2134
return layout

tests/run_default.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from dash_entrypoints import run_entrypoint
2+
3+
# from dash_entrypoints.misc import get_local_ip_address
4+
# from dash_entrypoints.multi_page.entrypoint import DEFAULT_VIEWS_MODULE
5+
6+
run_entrypoint(
7+
# app_name="TEST-APP",
8+
# ip_address=get_local_ip_address(),
9+
# views_module=DEFAULT_VIEWS_MODULE,
10+
# port=9050,
11+
debug=True,
12+
)

0 commit comments

Comments
 (0)