Skip to content

Commit

Permalink
wip implement dynamic navs
Browse files Browse the repository at this point in the history
  • Loading branch information
cpsievert committed Mar 1, 2022
1 parent c8c9190 commit 94dab30
Show file tree
Hide file tree
Showing 13 changed files with 778 additions and 806 deletions.
4 changes: 4 additions & 0 deletions docs/source/index.rst
Expand Up @@ -89,6 +89,10 @@ Create segments of UI content.
ui.navs_pill
ui.navs_pill_card
ui.navs_pill_list
ui.nav_insert
ui.nav_remove
ui.nav_show
ui.nav_hide


UI panels
Expand Down
57 changes: 57 additions & 0 deletions shiny/examples/nav_insert/app.py
@@ -0,0 +1,57 @@
from shiny import *

app_ui = ui.page_fluid(
ui.layout_sidebar(
ui.panel_sidebar(
ui.input_action_button("add", "Add 'Dynamic' tab"),
ui.input_action_button("removeFoo", "Remove 'Foo' tabs"),
ui.input_action_button("addFoo", "Add New 'Foo' tab"),
),
ui.panel_main(
ui.navs_tab(
ui.nav("Hello", "This is the hello tab"),
ui.nav("Foo", "This is the Foo tab", value="Foo"),
ui.nav_menu(
"Static",
ui.nav("Static 1", "Static 1", value="s1"),
ui.nav("Static 2", "Static 2", value="s2"),
value="Menu",
),
id="tabs",
),
),
)
)


def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect()
@event(input.add)
def _():
id = "Dynamic-" + str(input.add())
ui.nav_insert(
"tabs",
ui.nav(id, id),
target="s2",
position="before",
)

@reactive.Effect()
@event(input.removeFoo)
def _():
ui.nav_remove("tabs", target="Foo")

@reactive.Effect()
@event(input.addFoo)
def _():
n = str(input.addFoo())
ui.nav_insert(
"tabs",
ui.nav("Foo-" + n, "This is the new Foo-" + n + " tab", value="Foo"),
target="Menu",
position="before",
select=True,
)


app = App(app_ui, server, debug=True)
48 changes: 48 additions & 0 deletions shiny/examples/nav_show/app.py
@@ -0,0 +1,48 @@
from shiny import *

app_ui = ui.page_navbar(
ui.nav(
"Home",
ui.input_action_button("hideTab", "Hide 'Foo' tab"),
ui.input_action_button("showTab", "Show 'Foo' tab"),
ui.input_action_button("hideMenu", "Hide 'More' nav_menu"),
ui.input_action_button("showMenu", "Show 'More' nav_menu"),
),
ui.nav("Foo", "This is the foo tab"),
ui.nav("Bar", "This is the bar tab"),
ui.nav_menu(
"More",
ui.nav("Table", "Table page"),
ui.nav("About", "About page"),
"------",
"Even more!",
ui.nav("Email", "Email page"),
),
title="Navbar page",
id="tabs",
)


def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect()
@event(input.hideTab)
def _():
ui.nav_hide("tabs", target="Foo")

@reactive.Effect()
@event(input.showTab)
def _():
ui.nav_show("tabs", target="Foo")

@reactive.Effect()
@event(input.hideMenu)
def _():
ui.nav_hide("tabs", target="More")

@reactive.Effect()
@event(input.showMenu)
def _():
ui.nav_show("tabs", target="More")


app = App(app_ui, server)
1 change: 1 addition & 0 deletions shiny/ui/__init__.py
Expand Up @@ -19,6 +19,7 @@
from ._insert import *
from ._modal import *
from ._navs import *
from ._navs_dynamic import *
from ._notification import *
from ._output import *
from ._page import *
Expand Down
3 changes: 2 additions & 1 deletion shiny/ui/_navs.py
Expand Up @@ -25,6 +25,7 @@

from .._docstring import add_example
from ._html_dependencies import nav_deps
from .._utils import drop_none


@add_example()
Expand Down Expand Up @@ -678,4 +679,4 @@ def navs_bar(

def _nav_tag(name: str, *args: TagChildArg, **kwargs: JSXTagAttrArg) -> JSXTag:
tag = jsx_tag_create("bslib." + name)
return tag(nav_deps(), *args, **kwargs)
return tag(nav_deps(), *args, **drop_none(kwargs))
199 changes: 199 additions & 0 deletions shiny/ui/_navs_dynamic.py
@@ -0,0 +1,199 @@
__all__ = (
"nav_insert",
"nav_remove",
"nav_hide",
"nav_show",
)

import sys
from typing import Optional

if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal

from htmltools import JSXTag

from .._docstring import add_example
from ._input_update import update_navs
from ._navs import navs_hidden
from ..session import Session, require_active_session
from .._utils import run_coro_sync


@add_example()
def nav_insert(
id: str,
nav: JSXTag,
target: Optional[str] = None,
position: Literal["after", "before"] = "after",
select: bool = False,
session: Optional[Session] = None,
) -> None:
"""
Insert a new nav item into a navigation container.
Parameters
----------
id
The ``id`` of the relevant navigation container (i.e., ``navs_*()`` object).
nav
The navigation item to insert (typically a :func:`shiny.ui.nav` or
:func:`shiny.ui.nav_menu`).
target
The ``value`` of an existing :func:`shiny.ui.nav` item, next to which tab will
be added.
position
The position of the new nav item relative to the target nav item.
select
Whether the nav item should be selected upon insertion.
session
A :class:`~shiny.Session` instance. If not provided, it is inferred via
:func:`~shiny.session.get_current_session`.
See Also
--------
~nav_remove
~nav_show
~nav_hide
~shiny.ui.nav
"""

session = require_active_session(session)
# TODO: do we need to namespace the id?
# id = session.ns(id)

# The currrent JSX implementation of nav items is not smart enough to
# to render without a navs container (maybe it could?), so we need to
# wrap in one and also notify the JSX logic to not select the nav
# (shiny.js handles that part via the select parameter).
jsx_tag = navs_hidden(nav, selected=False) # type: ignore

msg = {
"inputId": id,
"menuName": None,
"target": target,
"position": position,
"select": select,
"jsxTag": session._process_ui(jsx_tag),
}

def callback() -> None:
run_coro_sync(session._send_message({"shiny-insert-tab": msg}))

session.on_flush(callback, once=True)


def nav_remove(id: str, target: str, session: Optional[Session] = None) -> None:
"""
Remove a nav item from a navigation container.
Parameters
----------
id
The ``id`` of the relevant navigation container (i.e., ``navs_*()`` object).
target
The ``value`` of an existing :func:`shiny.ui.nav` item to remove.
session
A :class:`~shiny.Session` instance. If not provided, it is inferred via
:func:`~shiny.session.get_current_session`.
See Also
--------
~nav_insert
~nav_show
~nav_hide
~shiny.ui.nav
"""

session = require_active_session(session)
# TODO: do we need to namespace the id?
# id = session.ns(id)

msg = {"inputId": id, "target": target}

def callback() -> None:
run_coro_sync(session._send_message({"shiny-remove-tab": msg}))

session.on_flush(callback, once=True)


def nav_show(
id: str, target: str, select: bool = False, session: Optional[Session] = None
) -> None:
"""
Show a navigation item
Parameters
----------
id
The ``id`` of the relevant navigation container (i.e., ``navs_*()`` object).
target
The ``value`` of an existing :func:`shiny.ui.nav` item to show.
select
Whether the nav item's content should also be shown.
session
A :class:`~shiny.Session` instance. If not provided, it is inferred via
:func:`~shiny.session.get_current_session`.
Note
----
For ``nav_show()`` to be relevant/useful, a :func:`shiny.ui.nav` item must
have been hidden using :func:`~nav_hide`.
See Also
--------
~nav_hide
~nav_insert
~nav_remove
~shiny.ui.nav
"""

session = require_active_session(session)

if select:
update_navs(id, selected=target)

# TODO: do we need to namespace the id?
# id = session.ns(id)
msg = {"inputId": id, "target": target, "type": "show"}

def callback() -> None:
run_coro_sync(session._send_message({"shiny-change-tab-visibility": msg}))

session.on_flush(callback, once=True)


def nav_hide(id: str, target: str, session: Optional[Session] = None) -> None:
"""
Hide a navigation item
Parameters
----------
id
The ``id`` of the relevant navigation container (i.e., ``navs_*()`` object).
target
The ``value`` of an existing :func:`shiny.ui.nav` item to hide.
session
A :class:`~shiny.Session` instance. If not provided, it is inferred via
:func:`~shiny.session.get_current_session`.
See Also
--------
~nav_show
~nav_insert
~nav_remove
~shiny.ui.nav
"""

session = require_active_session(session)
# TODO: do we need to namespace the id?
# id = session.ns(id)

msg = {"inputId": id, "target": target, "type": "hide"}

def callback() -> None:
run_coro_sync(session._send_message({"shiny-change-tab-visibility": msg}))

session.on_flush(callback, once=True)
4 changes: 3 additions & 1 deletion shiny/www/shared/bslib/dist/navs.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion shiny/www/shared/bslib/dist/navs.min.js.map

Large diffs are not rendered by default.

0 comments on commit 94dab30

Please sign in to comment.