Skip to content

Commit 05644ec

Browse files
committed
wip implement dynamic navs
1 parent e09bd1e commit 05644ec

File tree

7 files changed

+319
-1
lines changed

7 files changed

+319
-1
lines changed

docs/source/index.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ Create segments of UI content.
136136
ui.navs_pill
137137
ui.navs_pill_card
138138
ui.navs_pill_list
139+
ui.nav_insert
140+
ui.nav_remove
141+
ui.nav_show
142+
ui.nav_hide
139143

140144

141145
UI panels

shiny/_modules.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
__all__ = ("Module",)
22

3-
from typing import Any, Callable, Optional
3+
from typing import Any, Callable, Optional, Dict
44

55
from htmltools import TagChildArg
66

@@ -117,6 +117,15 @@ def __init__(self, ns: str, parent_session: Session) -> None:
117117
def __getattr__(self, attr: str) -> Any:
118118
return getattr(self._parent, attr)
119119

120+
def send_input_message(self, id: str, message: Dict[str, object]) -> None:
121+
return super().send_input_message(self.ns(id), message)
122+
123+
def ns(self, id: Optional[str] = None) -> str:
124+
if id is None:
125+
return self._ns
126+
else:
127+
return self._ns + "-" + id
128+
120129

121130
@add_example()
122131
class Module:

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.navs_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, debug=True)

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/session/_session.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,9 @@ def _process_ui(self, ui: TagChildArg) -> RenderedDeps:
670670

671671
return {"deps": deps, "html": res["html"]}
672672

673+
def ns(self, id: Optional[str] = None) -> Optional[str]:
674+
return id
675+
673676

674677
# ======================================================================================
675678
# Inputs

shiny/ui/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from ._markdown import *
2121
from ._modal import *
2222
from ._navs import *
23+
from ._navs_dynamic import *
2324
from ._notification import *
2425
from ._output import *
2526
from ._page import *

shiny/ui/_navs_dynamic.py

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

0 commit comments

Comments
 (0)