diff --git a/CHANGELOG.md b/CHANGELOG.md index ee42b9ed3..94b18d8f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Bug fixes +* Fixed numerous issues related to programmatically updating selectize options. (#2053) + * `update_selectize(options=...)` no longer gets ignored when `server=False` (the default). + * `update_selectize(options=...)` now works as expected in a module. + * Fixed an issue with `update_selectize()` to properly display labels with HTML reserved characters like "&" (#1330) * Fixed an issue with `ui.Chat()` sometimes wanting to scroll a parent element. (#1996) diff --git a/shiny/ui/_input_update.py b/shiny/ui/_input_update.py index cad0af229..7395e764b 100644 --- a/shiny/ui/_input_update.py +++ b/shiny/ui/_input_update.py @@ -722,22 +722,20 @@ def update_selectize( session = require_active_session(session) + if options is not None: + cfg = tags.script( + json.dumps(options), + type="application/json", + data_for=resolve_id(id), + data_eval=json.dumps(extract_js_keys(options)), + ) + session.send_input_message(id, {"config": cfg.get_html_string()}) + if not server: return update_select( id, label=label, choices=choices, selected=selected, session=session ) - if options is not None: - cfg = TagList( - tags.script( - json.dumps(options), - type="application/json", - data_for=id, - data_eval=json.dumps(extract_js_keys(options)), - ) - ) - session.send_input_message(id, drop_none({"config": cfg.get_html_string()})) - # Transform choices to a list of dicts (this is the form the client wants) # [{"label": "Foo", "value": "foo", "optgroup": "foo"}, ...] flat_choices: list[FlatSelectChoice] = [] diff --git a/tests/playwright/shiny/components/selectize/app.py b/tests/playwright/shiny/components/selectize/app.py new file mode 100644 index 000000000..78f4400a5 --- /dev/null +++ b/tests/playwright/shiny/components/selectize/app.py @@ -0,0 +1,34 @@ +from shiny import App, Inputs, Outputs, Session, module, reactive, ui + + +@module.ui +def reprex_selectize_ui(label: str): + return ui.input_selectize("x", label, choices=[], multiple=True) + + +@module.server +def reprex_selectize_server( + input: Inputs, output: Outputs, session: Session, server: bool = True +): + @reactive.effect + def _(): + ui.update_selectize( + "x", + choices=[f"Foo {i}" for i in range(3)], + server=server, + options={"placeholder": "Search"}, + ) + + +app_ui = ui.page_fluid( + reprex_selectize_ui("serverside", "Server"), + reprex_selectize_ui("clientside", "Client"), +) + + +def server(input: Inputs, output: Outputs, session: Session): + reprex_selectize_server("serverside", server=True) + reprex_selectize_server("clientside", server=False) + + +app = App(app_ui, server, debug=True) diff --git a/tests/playwright/shiny/components/selectize/test_selectize.py b/tests/playwright/shiny/components/selectize/test_selectize.py new file mode 100644 index 000000000..27e9585be --- /dev/null +++ b/tests/playwright/shiny/components/selectize/test_selectize.py @@ -0,0 +1,23 @@ +# import pytest +from playwright.sync_api import Page, expect + +from shiny.playwright import controller +from shiny.run import ShinyAppProc + + +def test_selectize(page: Page, local_app: ShinyAppProc) -> None: + page.goto(local_app.url) + + expect(page.get_by_placeholder("Search")).to_have_count(2) + + selectize_menu = controller.InputSelectize(page, "serverside-x") + selectize_menu.expect_choices(["Foo 0", "Foo 1", "Foo 2"]) + selectize_menu.expect_multiple(True) + selectize_menu.set(["Foo 0", "Foo 1"]) + selectize_menu.expect_selected(["Foo 0", "Foo 1"]) + + selectize_menu2 = controller.InputSelectize(page, "clientside-x") + selectize_menu2.expect_choices(["Foo 0", "Foo 1", "Foo 2"]) + selectize_menu2.expect_multiple(True) + selectize_menu2.set(["Foo 0", "Foo 1"]) + selectize_menu2.expect_selected(["Foo 0", "Foo 1"])