Skip to content

Commit 44de731

Browse files
author
Eric Smyth
committed
Initial save to/load from URL
1 parent 120446b commit 44de731

File tree

4 files changed

+106
-21
lines changed

4 files changed

+106
-21
lines changed

src/chime_dash/app/pages/root.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@
77
"""
88
from collections import OrderedDict
99

10-
from dash_bootstrap_components import Container, Row
1110
from dash_bootstrap_components.themes import BOOTSTRAP
1211
from dash_html_components import Div
13-
from dash_core_components import Store
12+
from dash_core_components import Location, Store
1413

1514
from chime_dash.app.components.base import Page
1615
from chime_dash.app.components.navbar import Navbar
@@ -32,13 +31,13 @@ class Root(Page):
3231
def __init__(self, language, defaults):
3332
"""
3433
"""
35-
super().__init__(language, defaults)
3634
self.components = OrderedDict(
3735
navbar=Navbar(language, defaults),
3836
sidebar=Sidebar(language, defaults),
3937
# todo subscribe to changes to URL and select page appropriately
4038
index=Index(language, defaults),
4139
)
40+
super().__init__(language, defaults)
4241

4342
def get_html(self):
4443
"""Glues individual setup components together
@@ -51,6 +50,7 @@ def get_html(self):
5150
children=self.components["sidebar"].html
5251
+ self.components["index"].html
5352
),
54-
Store(id="root-store")
53+
Store(id="root-store"),
54+
Location(id='location')
5555
]
5656
)

src/chime_dash/app/pages/sidebar.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from dash.development.base_component import ComponentMeta
1111
from dash_html_components import Nav, Div
12+
from dash_core_components import Store
1213

1314
from chime_dash.app.components.base import Page
1415
from chime_dash.app.utils import ReadOnlyDict
@@ -117,7 +118,9 @@ class Sidebar(Page):
117118
def get_html(self) -> List[ComponentMeta]:
118119
"""Initializes the view
119120
"""
120-
elements = []
121+
elements = [
122+
Store(id="sidebar-store")
123+
]
121124
for idx, data in _SIDEBAR_ELEMENTS.items():
122125
if data["type"] == "number":
123126
element = create_number_input(idx, data, self.content, self.defaults)

src/chime_dash/app/services/callbacks.py

Lines changed: 97 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
from typing import List
2-
from datetime import date, datetime
2+
from datetime import datetime
3+
from collections import OrderedDict
4+
from urllib.parse import parse_qsl, urlencode
5+
from dash.exceptions import PreventUpdate
6+
from dateutil.parser import parse as parse_date
37

48
from chime_dash.app.utils.callbacks import ChimeCallback, register_callbacks
59
from chime_dash.app.utils import (
@@ -30,13 +34,13 @@ def toggle_tables(switch_value):
3034
return get_n_switch_values(switch_value, 3)
3135

3236
@staticmethod
33-
def handle_model_change(i, pars_json):
37+
def handle_model_change(i, sidebar_data):
3438
model = {}
3539
pars = None
3640
result = []
3741
viz_kwargs = {}
38-
if pars_json:
39-
pars = parameters_deserializer(pars_json)
42+
if sidebar_data:
43+
pars = parameters_deserializer(sidebar_data["parameters"])
4044
model = SimSirModel(pars)
4145
viz_kwargs = dict(
4246
labels=pars.labels,
@@ -53,9 +57,8 @@ def handle_model_change(i, pars_json):
5357
return result
5458

5559
def __init__(self, component_instance):
56-
def handle_model_change_helper(*args, **kwargs):
57-
pars_json = args[1]
58-
return IndexCallbacks.handle_model_change(component_instance, pars_json)
60+
def handle_model_change_helper(sidebar_mod, sidebar_data):
61+
return IndexCallbacks.handle_model_change(component_instance, sidebar_data)
5962

6063
super().__init__(
6164
component_instance=component_instance,
@@ -75,7 +78,7 @@ def handle_model_change_helper(*args, **kwargs):
7578
callback_fn=IndexCallbacks.toggle_tables
7679
),
7780
ChimeCallback( # If the parameters or model change, update the text
78-
changed_elements={"root-store": "modified_timestamp"},
81+
changed_elements={"sidebar-store": "modified_timestamp"},
7982
dom_updates={
8083
"intro": "children",
8184
"more_intro": "children",
@@ -90,7 +93,7 @@ def handle_model_change_helper(*args, **kwargs):
9093
"SIR_download": "href",
9194
},
9295
callback_fn=handle_model_change_helper,
93-
stores=['root-store'],
96+
stores=["sidebar-store"],
9497
)
9598
]
9699
)
@@ -107,11 +110,14 @@ def get_formated_values(i, input_values):
107110
result[key] = False if result[key] == [True] else True
108111
elif input_type == "date":
109112
value = result[key]
110-
result[key] = datetime.strptime(value, "%Y-%m-%d").date() if value else value
113+
try:
114+
result[key] = datetime.strptime(value, "%Y-%m-%d").date() if value else value
115+
except ValueError:
116+
pass
111117
return result
112118

113119
@staticmethod
114-
def update_parameters(i, *input_values) -> List[str]:
120+
def update_parameters(i, *input_values) -> List[dict]:
115121
"""Reads html form outputs and converts them to a parameter instance
116122
117123
Returns Parameters
@@ -137,7 +143,7 @@ def update_parameters(i, *input_values) -> List[str]:
137143
),
138144
max_y_axis=inputs_dict.get("max_y_axis_value", None),
139145
)
140-
return [parameters_serializer(pars)]
146+
return [{"inputs_dict": inputs_dict, "parameters": parameters_serializer(pars)}]
141147

142148
def __init__(self, component_instance):
143149
def update_parameters_helper(*args, **kwargs):
@@ -148,17 +154,93 @@ def update_parameters_helper(*args, **kwargs):
148154
callbacks=[
149155
ChimeCallback(
150156
changed_elements=component_instance.input_value_map,
151-
dom_updates={"root-store": "data"},
157+
dom_updates={"sidebar-store": "data"},
152158
callback_fn=update_parameters_helper,
153-
stores=['root-store'],
154159
)
155160
]
156161
)
157162

158163

164+
# todo Add tons of tests and validation because there be dragons
159165
class RootCallbacks(ComponentCallbacks):
166+
@staticmethod
167+
def try_parsing_number(v):
168+
try:
169+
return int(v)
170+
except ValueError:
171+
try:
172+
return float(v)
173+
except ValueError:
174+
return v
175+
176+
@staticmethod
177+
def get_inputs(val_dict, inputs_keys):
178+
# todo handle versioning of inputs
179+
return OrderedDict((key, value) for key, value in val_dict.items() if key in inputs_keys)
180+
181+
@staticmethod
182+
def parse_hash(hash_str, sidebar_input_types):
183+
hash_dict = dict(parse_qsl(hash_str[1:]))
184+
for key, value in hash_dict.items():
185+
value_type = sidebar_input_types[key]
186+
if value_type == "number":
187+
parsed_value = RootCallbacks.try_parsing_number(value)
188+
# elif value_type == "date":
189+
# parsed_value = parse_date(value)
190+
else:
191+
parsed_value = value
192+
hash_dict[key] = parsed_value
193+
return hash_dict
194+
195+
@staticmethod
196+
def hash_changed(sidebar_input_types, hash_str=None, root_data=None):
197+
if hash_str:
198+
hash_dict = RootCallbacks.parse_hash(hash_str, sidebar_input_types)
199+
result = RootCallbacks.get_inputs(hash_dict, sidebar_input_types.keys())
200+
# Don't update the data store if it already contains the same data
201+
if result == root_data:
202+
raise PreventUpdate
203+
else:
204+
raise PreventUpdate
205+
return [result]
206+
207+
@staticmethod
208+
def stores_changed(inputs_keys, root_mod, sidebar_mod, root_data, sidebar_data):
209+
root_modified = root_mod or 0
210+
sidebar_modified = sidebar_mod or 0
211+
if root_modified < sidebar_modified:
212+
inputs_dict = sidebar_data["inputs_dict"]
213+
new_val = RootCallbacks.get_inputs(inputs_dict, inputs_keys)
214+
elif root_modified > sidebar_modified:
215+
new_val = RootCallbacks.get_inputs(root_data, inputs_keys)
216+
else:
217+
raise PreventUpdate
218+
return ["#{}".format(urlencode(new_val))] + list(new_val.values())
219+
160220
def __init__(self, component_instance):
221+
sidebar = component_instance.components["sidebar"]
222+
sidebar_inputs = sidebar.input_value_map
223+
sidebar_input_types = sidebar.input_type_map
224+
225+
def hash_changed_helper(hash_str=None, root_data=None):
226+
return RootCallbacks.hash_changed(sidebar_input_types, hash_str, root_data)
227+
228+
def stores_changed_helper(root_mod, sidebar_mod, root_data, sidebar_data):
229+
return RootCallbacks.stores_changed(sidebar_inputs.keys(), root_mod, sidebar_mod, root_data, sidebar_data)
161230
super().__init__(
162231
component_instance=component_instance,
163-
callbacks=[]
232+
callbacks=[
233+
ChimeCallback(
234+
changed_elements={"location": "hash"},
235+
dom_updates={"root-store": "data"},
236+
callback_fn=hash_changed_helper,
237+
stores=["root-store"],
238+
),
239+
ChimeCallback(
240+
changed_elements={"root-store": "modified_timestamp", "sidebar-store": "modified_timestamp"},
241+
dom_updates={"location": "hash", **sidebar_inputs},
242+
callback_fn=stores_changed_helper,
243+
stores=["root-store", "sidebar-store"],
244+
),
245+
]
164246
)

src/chime_dash/app/utils/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def __len__(self):
4040
def __iter__(self):
4141
return iter(self._data)
4242

43-
def __keys__(self):
43+
def keys(self):
4444
return self._data.keys()
4545

4646

0 commit comments

Comments
 (0)