Skip to content

Commit d761a5d

Browse files
cpsievertjcheng5
authored andcommitted
Implement dynamic navs (i.e., nav_insert(), nav_remove(), nav_show(), nav_hide())
1 parent 02945c7 commit d761a5d

File tree

4 files changed

+314
-0
lines changed

4 files changed

+314
-0
lines changed

shiny/examples/nav_insert/app.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from shiny import *
2+
3+
app_ui = ui.page_fluid(
4+
ui.layout_sidebar(
5+
ui.panel_sidebar(
6+
ui.input_action_button("add", "Add 'Dynamic' tab"),
7+
ui.input_action_button("removeFoo", "Remove 'Foo' tabs"),
8+
ui.input_action_button("addFoo", "Add New 'Foo' tab"),
9+
),
10+
ui.panel_main(
11+
ui.navset_tab(
12+
ui.nav("Hello", "This is the hello tab"),
13+
ui.nav("Foo", "This is the Foo tab", value="Foo"),
14+
ui.nav_menu(
15+
"Static",
16+
ui.nav("Static 1", "Static 1", value="s1"),
17+
ui.nav("Static 2", "Static 2", value="s2"),
18+
value="Menu",
19+
),
20+
id="tabs",
21+
),
22+
),
23+
)
24+
)
25+
26+
27+
def server(input: Inputs, output: Outputs, session: Session):
28+
@reactive.Effect()
29+
@event(input.add)
30+
def _():
31+
id = "Dynamic-" + str(input.add())
32+
ui.nav_insert(
33+
"tabs",
34+
ui.nav(id, id),
35+
target="s2",
36+
position="before",
37+
)
38+
39+
@reactive.Effect()
40+
@event(input.removeFoo)
41+
def _():
42+
ui.nav_remove("tabs", target="Foo")
43+
44+
@reactive.Effect()
45+
@event(input.addFoo)
46+
def _():
47+
n = str(input.addFoo())
48+
ui.nav_insert(
49+
"tabs",
50+
ui.nav("Foo-" + n, "This is the new Foo-" + n + " tab", value="Foo"),
51+
target="Menu",
52+
position="before",
53+
select=True,
54+
)
55+
56+
57+
app = App(app_ui, server)

shiny/examples/nav_show/app.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from shiny import *
2+
3+
app_ui = ui.page_navbar(
4+
ui.nav(
5+
"Home",
6+
ui.input_action_button("hideTab", "Hide 'Foo' tab"),
7+
ui.input_action_button("showTab", "Show 'Foo' tab"),
8+
ui.input_action_button("hideMenu", "Hide 'More' nav_menu"),
9+
ui.input_action_button("showMenu", "Show 'More' nav_menu"),
10+
),
11+
ui.nav("Foo", "This is the foo tab"),
12+
ui.nav("Bar", "This is the bar tab"),
13+
ui.nav_menu(
14+
"More",
15+
ui.nav("Table", "Table page"),
16+
ui.nav("About", "About page"),
17+
"------",
18+
"Even more!",
19+
ui.nav("Email", "Email page"),
20+
),
21+
title="Navbar page",
22+
id="tabs",
23+
)
24+
25+
26+
def server(input: Inputs, output: Outputs, session: Session):
27+
@reactive.Effect()
28+
@event(input.hideTab)
29+
def _():
30+
ui.nav_hide("tabs", target="Foo")
31+
32+
@reactive.Effect()
33+
@event(input.showTab)
34+
def _():
35+
ui.nav_show("tabs", target="Foo")
36+
37+
@reactive.Effect()
38+
@event(input.hideMenu)
39+
def _():
40+
ui.nav_hide("tabs", target="More")
41+
42+
@reactive.Effect()
43+
@event(input.showMenu)
44+
def _():
45+
ui.nav_show("tabs", target="More")
46+
47+
48+
app = App(app_ui, server)

shiny/ui/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@
6666
navset_hidden,
6767
navset_bar,
6868
)
69+
from ._navs_dynamic import (
70+
nav_hide,
71+
nav_insert,
72+
nav_remove,
73+
nav_show,
74+
)
6975
from ._notification import notification_show, notification_remove
7076
from ._output import (
7177
output_plot,
@@ -179,6 +185,10 @@
179185
"navset_pill_list",
180186
"navset_hidden",
181187
"navset_bar",
188+
"nav_hide",
189+
"nav_insert",
190+
"nav_remove",
191+
"nav_show",
182192
"notification_show",
183193
"notification_remove",
184194
"output_plot",

shiny/ui/_navs_dynamic.py

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
__all__ = (
2+
"nav_insert",
3+
"nav_remove",
4+
"nav_hide",
5+
"nav_show",
6+
)
7+
8+
import sys
9+
from typing import Optional, Union
10+
11+
if sys.version_info >= (3, 8):
12+
from typing import Literal
13+
else:
14+
from typing_extensions import Literal
15+
16+
from .._docstring import add_example
17+
from .._utils import run_coro_sync
18+
from ..session import Session, require_active_session
19+
from ..types import NavSetArg
20+
from ._input_update import update_navs
21+
from ._navs import menu_string_as_nav
22+
23+
24+
@add_example()
25+
def nav_insert(
26+
id: str,
27+
nav: Union[NavSetArg, str],
28+
target: Optional[str] = None,
29+
position: Literal["after", "before"] = "after",
30+
select: bool = False,
31+
session: Optional[Session] = None,
32+
) -> None:
33+
"""
34+
Insert a new nav item into a navigation container.
35+
36+
Parameters
37+
----------
38+
id
39+
The ``id`` of the relevant navigation container (i.e., ``navset_*()`` object).
40+
nav
41+
The navigation item to insert (typically a :func:`~shiny.ui.nav` or
42+
:func:`~shiny.ui.nav_menu`). A :func:`~shiny.ui.nav_menu` isn't allowed when the
43+
``target`` references an :func:`~shiny.ui.nav_menu` (or an item within it). A
44+
string is only allowed when the ``target`` references a
45+
:func:`~shiny.ui.nav_menu`.
46+
target
47+
The ``value`` of an existing :func:`shiny.ui.nav` item, next to which tab will
48+
be added.
49+
position
50+
The position of the new nav item relative to the target nav item.
51+
select
52+
Whether the nav item should be selected upon insertion.
53+
session
54+
A :class:`~shiny.Session` instance. If not provided, it is inferred via
55+
:func:`~shiny.session.get_current_session`.
56+
57+
See Also
58+
--------
59+
~nav_remove
60+
~nav_show
61+
~nav_hide
62+
~shiny.ui.nav
63+
"""
64+
65+
session = require_active_session(session)
66+
67+
# N.B. this is only sensible if the target is a menu, but we don't know that,
68+
# which could cause confusion of we decide to support top-level strings at some
69+
# in the future.
70+
if isinstance(nav, str):
71+
nav = menu_string_as_nav(nav)
72+
73+
# N.B. shiny.js' is smart enough to know how to add active classes and href/id attrs
74+
li_tag, div_tag = nav.resolve(
75+
selected=None, context=dict(tabsetid="tsid", index="id")
76+
)
77+
78+
msg = {
79+
"inputId": session.ns(id),
80+
"liTag": session._process_ui(li_tag),
81+
"divTag": session._process_ui(div_tag),
82+
"menuName": None,
83+
"target": target,
84+
"position": position,
85+
"select": select,
86+
}
87+
88+
def callback() -> None:
89+
run_coro_sync(session._send_message({"shiny-insert-tab": msg}))
90+
91+
session.on_flush(callback, once=True)
92+
93+
94+
def nav_remove(id: str, target: str, session: Optional[Session] = None) -> None:
95+
"""
96+
Remove a nav item from a navigation container.
97+
98+
Parameters
99+
----------
100+
id
101+
The ``id`` of the relevant navigation container (i.e., ``navset_*()`` object).
102+
target
103+
The ``value`` of an existing :func:`shiny.ui.nav` item to remove.
104+
session
105+
A :class:`~shiny.Session` instance. If not provided, it is inferred via
106+
:func:`~shiny.session.get_current_session`.
107+
108+
See Also
109+
--------
110+
~nav_insert
111+
~nav_show
112+
~nav_hide
113+
~shiny.ui.nav
114+
"""
115+
116+
session = require_active_session(session)
117+
118+
msg = {"inputId": session.ns(id), "target": target}
119+
120+
def callback() -> None:
121+
run_coro_sync(session._send_message({"shiny-remove-tab": msg}))
122+
123+
session.on_flush(callback, once=True)
124+
125+
126+
def nav_show(
127+
id: str, target: str, select: bool = False, session: Optional[Session] = None
128+
) -> None:
129+
"""
130+
Show a navigation item
131+
132+
Parameters
133+
----------
134+
id
135+
The ``id`` of the relevant navigation container (i.e., ``navset_*()`` object).
136+
target
137+
The ``value`` of an existing :func:`shiny.ui.nav` item to show.
138+
select
139+
Whether the nav item's content should also be shown.
140+
session
141+
A :class:`~shiny.Session` instance. If not provided, it is inferred via
142+
:func:`~shiny.session.get_current_session`.
143+
144+
Note
145+
----
146+
For ``nav_show()`` to be relevant/useful, a :func:`shiny.ui.nav` item must
147+
have been hidden using :func:`~nav_hide`.
148+
149+
See Also
150+
--------
151+
~nav_hide
152+
~nav_insert
153+
~nav_remove
154+
~shiny.ui.nav
155+
"""
156+
157+
session = require_active_session(session)
158+
159+
if select:
160+
update_navs(id, selected=target)
161+
162+
msg = {"inputId": session.ns(id), "target": target, "type": "show"}
163+
164+
def callback() -> None:
165+
run_coro_sync(session._send_message({"shiny-change-tab-visibility": msg}))
166+
167+
session.on_flush(callback, once=True)
168+
169+
170+
def nav_hide(id: str, target: str, session: Optional[Session] = None) -> None:
171+
"""
172+
Hide a navigation item
173+
174+
Parameters
175+
----------
176+
id
177+
The ``id`` of the relevant navigation container (i.e., ``navset_*()`` object).
178+
target
179+
The ``value`` of an existing :func:`shiny.ui.nav` item to hide.
180+
session
181+
A :class:`~shiny.Session` instance. If not provided, it is inferred via
182+
:func:`~shiny.session.get_current_session`.
183+
184+
See Also
185+
--------
186+
~nav_show
187+
~nav_insert
188+
~nav_remove
189+
~shiny.ui.nav
190+
"""
191+
192+
session = require_active_session(session)
193+
194+
msg = {"inputId": session.ns(id), "target": target, "type": "hide"}
195+
196+
def callback() -> None:
197+
run_coro_sync(session._send_message({"shiny-change-tab-visibility": msg}))
198+
199+
session.on_flush(callback, once=True)

0 commit comments

Comments
 (0)