Skip to content

Commit 3f40bcd

Browse files
jcheng5wch
andauthored
Allow ui.update_slider to handle non-numeric values (#649)
Co-authored-by: Winston Chang <[email protected]>
1 parent 59ddbf2 commit 3f40bcd

File tree

4 files changed

+155
-3
lines changed

4 files changed

+155
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717

1818
### Bug fixes
1919

20+
* Using `update_slider` to update a slider's value to a `datetime` object or other non-numeric value would result in an error. (#649)
21+
2022
### Other changes
2123

2224
* Documentation updates. (#591)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
from __future__ import annotations
2+
3+
import datetime
4+
from typing import Any, Optional, Union
5+
6+
import shiny.experimental as x
7+
from shiny import App, Inputs, Outputs, Session, reactive, render, ui
8+
9+
start_time = datetime.datetime(2023, 7, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc)
10+
end_time = start_time + datetime.timedelta(hours=1)
11+
12+
from shiny import App, Inputs, Outputs, Session, module, reactive, render, ui
13+
14+
15+
@module.ui
16+
def slider_with_reset_ui(
17+
label: str,
18+
value: Union[
19+
datetime.datetime,
20+
tuple[datetime.datetime, datetime.datetime],
21+
list[datetime.datetime],
22+
],
23+
) -> ui.TagChild:
24+
return x.ui.card(
25+
ui.input_slider(
26+
"times",
27+
"Times",
28+
start_time,
29+
end_time,
30+
value,
31+
timezone="UTC",
32+
time_format="%H:%M:%S",
33+
),
34+
ui.output_text_verbatim("txt"),
35+
ui.div(ui.input_action_button("reset", label)),
36+
)
37+
38+
39+
@module.server
40+
def slider_with_reset_server(
41+
input: Inputs,
42+
output: Outputs,
43+
session: Session,
44+
*,
45+
min: Optional[datetime.datetime] = None,
46+
max: Optional[datetime.datetime] = None,
47+
value: Any = None,
48+
):
49+
@output
50+
@render.text
51+
def txt():
52+
if isinstance(input.times(), (tuple, list)):
53+
return " - ".join([str(x) for x in input.times()])
54+
else:
55+
return input.times()
56+
57+
@reactive.Effect
58+
@reactive.event(input.reset)
59+
def reset_time():
60+
ui.update_slider("times", min=min, max=max, value=value)
61+
62+
63+
app_ui = ui.page_fluid(
64+
x.ui.layout_column_wrap(
65+
"400px",
66+
slider_with_reset_ui("one", "Jump to end", start_time),
67+
slider_with_reset_ui("two", "Select all", (start_time, start_time)),
68+
slider_with_reset_ui("three", "Select all", [start_time, start_time]),
69+
slider_with_reset_ui("four", "Extend min", [start_time, end_time]),
70+
slider_with_reset_ui("five", "Extend max", [start_time, end_time]),
71+
slider_with_reset_ui("six", "Extend min and max", [start_time, end_time]),
72+
),
73+
class_="p-3",
74+
)
75+
76+
77+
def server(input: Inputs, output: Outputs, session: Session):
78+
slider_with_reset_server("one", value=end_time)
79+
slider_with_reset_server("two", value=(start_time, end_time))
80+
slider_with_reset_server("three", value=[start_time, end_time])
81+
slider_with_reset_server("four", min=start_time - datetime.timedelta(hours=1))
82+
slider_with_reset_server("five", max=end_time + datetime.timedelta(hours=1))
83+
slider_with_reset_server(
84+
"six",
85+
min=start_time - datetime.timedelta(hours=1),
86+
max=end_time + datetime.timedelta(hours=1),
87+
)
88+
89+
90+
app = App(app_ui, server)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from __future__ import annotations
2+
3+
from typing import Optional
4+
5+
from conftest import ShinyAppProc
6+
from controls import InputActionButton, InputSlider, OutputTextVerbatim
7+
from playwright.sync_api import Page, expect
8+
9+
10+
def test_slider_app(page: Page, local_app: ShinyAppProc) -> None:
11+
def check_case(
12+
id: str,
13+
*,
14+
value: tuple[Optional[str], Optional[str]] = (None, None),
15+
min: tuple[Optional[str], Optional[str]] = (None, None),
16+
max: tuple[Optional[str], Optional[str]] = (None, None),
17+
):
18+
slider_times = InputSlider(page, f"{id}-times")
19+
btn_reset = InputActionButton(page, f"{id}-reset")
20+
out_txt = OutputTextVerbatim(page, f"{id}-txt")
21+
22+
if value[0] is not None:
23+
out_txt.expect_value(value[0])
24+
if min[0] is not None:
25+
expect(slider_times.loc_irs.locator(".irs-min")).to_have_text(min[0])
26+
if max[0] is not None:
27+
expect(slider_times.loc_irs.locator(".irs-max")).to_have_text(max[0])
28+
29+
btn_reset.loc.click()
30+
31+
if value[1] is not None:
32+
out_txt.expect_value(value[1])
33+
if min[1] is not None:
34+
expect(slider_times.loc_irs.locator(".irs-min")).to_have_text(min[1])
35+
if max[1] is not None:
36+
expect(slider_times.loc_irs.locator(".irs-max")).to_have_text(max[1])
37+
38+
page.goto(local_app.url)
39+
40+
start_time = "2023-07-01 00:00:00"
41+
end_time = "2023-07-01 01:00:00"
42+
43+
check_case("one", value=(start_time, end_time))
44+
check_case(
45+
"two",
46+
value=(f"{start_time} - {start_time}", f"{start_time} - {end_time}"),
47+
)
48+
check_case(
49+
"three",
50+
value=(f"{start_time} - {start_time}", f"{start_time} - {end_time}"),
51+
)
52+
check_case("four", min=("00:00:00", "23:00:00"))
53+
check_case("five", max=("01:00:00", "02:00:00"))
54+
check_case("six", min=("00:00:00", "23:00:00"), max=("01:00:00", "02:00:00"))

shiny/ui/_input_update.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -775,8 +775,8 @@ def update_slider(
775775
session = require_active_session(session)
776776

777777
# Get any non-None value to see if the `data-type` may need to change
778-
val = value[0] if isinstance(value, tuple) else value
779-
present_val = next((x for x in [val, min, max]), None)
778+
val = value[0] if isinstance(value, (tuple, list)) else value
779+
present_val = next((x for x in [val, min, max] if x is not None), None)
780780

781781
data_type = None if present_val is None else _slider_type(present_val)
782782
if time_format is None and data_type and data_type[0:4] == "date":
@@ -785,10 +785,16 @@ def update_slider(
785785
min_num = None if min is None else _as_numeric(min)
786786
max_num = None if max is None else _as_numeric(max)
787787
step_num = None if step is None else _as_numeric(step)
788+
if isinstance(value, (tuple, list)):
789+
value_num = [_as_numeric(x) for x in value]
790+
elif value is not None:
791+
value_num = _as_numeric(value)
792+
else:
793+
value_num = None
788794

789795
msg = {
790796
"label": label,
791-
"value": value,
797+
"value": value_num,
792798
"min": min_num,
793799
"max": max_num,
794800
"step": step_num,

0 commit comments

Comments
 (0)