Skip to content

Commit

Permalink
Added basic outlet documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Alyxion committed Dec 11, 2024
1 parent 5036cbe commit f4f971e
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 40 deletions.
71 changes: 31 additions & 40 deletions nicegui/outlet.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,44 +16,46 @@


class Outlet:
"""An outlet allows the creation of single page applications which do not reload the page when navigating between
different views. The outlet is a container for multiple views and can contain further, nested outlets.
To define a new outlet, use the @ui.outlet decorator on a function which defines the layout of the outlet.
The layout function must be a generator function and contain a yield statement to separate the layout from the
actual content area. The yield can also be used to pass properties to the content are by return a dictionary
with the properties. Each property can be received as function argument in all nested views and outlets.
Once the outlet is defined, multiple views can be added to the outlet using the @<outlet_function>.view decorator on
a function."""

def __init__(self,
path: str,
outlet_builder: Optional[Callable] = None,
browser_history: bool = True,
parent: Optional['Outlet'] = None,
on_instance_created: Optional[Callable[['SinglePageRouter'], None]] = None,
on_navigate: Optional[Callable[[str], Optional[Union[SinglePageTarget, str]]]] = None,
router_class: Optional[Callable[..., SinglePageRouter]] = None,
**kwargs) -> None:
"""
:param path: the base path of the single page router.
:param outlet_builder: A layout definition function which defines the layout of the page. The layout builder
must be a generator function and contain a yield statement to separate the layout from the content area.
:param layout_builder: A layout builder function which defines the layout of the page. The layout builder
must be a generator function and contain a yield statement to separate the layout from the content area.
:param browser_history: Optional flag to enable or disable the browser history management. Default is True.
:param on_instance_created: Optional callback which is called when a new instance is created. Each browser tab
or window is a new instance. This can be used to initialize the state of the application.
:param on_navigate: Optional callback which is called when a navigation event is triggered. Can be used to
"""Outlets: Building Single-Page Applications
In NiceGUI, outlets facilitate the creation of single-page applications by enabling seamless transitions between
views without full page reloads. The outlet decorator allows defining the layout of the page once at the top level
and then adding multiple views that are rendered inside the layout.
The decorated function has to be a generator function containing a `yield` statement separating the layout from
the content area. The actual content is added at the point where the yield statement is reached.
The views of the single page application are defined using the `outlet.view` decorator which works quite
similar to the `ui.page` decorator but instead of specifying the path directly, it uses a relative path starting
from the outlet's base path. `ui.page` parameters like `title`, query parameters or path parameters can be used
as well.
Notes:
- The `yield` statement can be used to return a dictionary of keyword arguments that are passed to each
of its views. Such keyword arguments can be references to shared UI elements such as the sidebar or header but
also any other data that should be shared between the views.
- As each page instance gets its own outlet instance, the state of the application can be stored in the outlet
and passed via the keyword arguments to the views.
- Outlets can be nested to create complex single page applications with multiple levels of navigation.
- Linking and navigating via `ui.navigate` or `ui.link` works for outlet views as for classic pages.
:param path: route of the new page (path must start with '/')
:param on_instance_created: Called when a new instance is created. Each browser tab creates is a new instance.
This can be used to initialize the state of the application.
:param on_navigate: Called when a navigation event is triggered. Can be used to
prevent or modify the navigation. Return the new URL if the navigation should be allowed, modify the URL
or return None to prevent the navigation.
:param router_class: Optional class which is used to create the router instance. The class must be a subclass
of SinglePageRouter. If not provided, the default SinglePageRouter is used.
If the class defines a method with the name 'page_template', this method is used as the outlet builder.
:param router_class: Class which is used to create the router instance. By default, SinglePageRouter is used.
:param parent: The parent outlet of this outlet.
:param kwargs: Additional arguments
:param kwargs: additional keyword arguments passed to FastAPI's @app.get method
"""
super().__init__()
self.routes: Dict[str, 'OutletPath'] = {}
Expand All @@ -62,7 +64,7 @@ def __init__(self,
self.excluded_paths: Set[str] = set()
self.on_instance_created: Optional[Callable] = on_instance_created
self.on_navigate: Optional[Callable[[str], Optional[Union[SinglePageTarget, str]]]] = on_navigate
self.use_browser_history = browser_history
self.use_browser_history = True
self.page_template = None
self._setup_configured = False
self.parent_config = parent
Expand All @@ -74,12 +76,6 @@ def __init__(self,
self.outlet_builder: Optional[Callable] = None
if parent is None:
Client.top_level_outlets[path] = self
if router_class is not None:
# check if class defines outlet builder function
if hasattr(router_class, PAGE_TEMPLATE_METHOD_NAME):
outlet_builder = getattr(router_class, PAGE_TEMPLATE_METHOD_NAME)
if outlet_builder is not None:
self(outlet_builder)

def build_page_template(self, **kwargs):
"""Setups the content area for the single page router"""
Expand Down Expand Up @@ -137,16 +133,11 @@ def setup_page(self, overwrite=False) -> Self:
self.excluded_paths.add(route)
if overwrite:
continue
if self.base_path.startswith(route.rstrip('/') + '/'): # '/sub_router' after '/' - forbidden
raise ValueError(f'Another router with path "{route.rstrip("/")}/*" is already registered which '
f'includes this router\'s base path "{self.base_path}". You can declare the nested '
f'router first to prioritize it and avoid this issue.')

@ui.page(self.base_path, **self.page_kwargs)
@ui.page(f'{self.base_path}' + '{_:path}', **self.page_kwargs) # all other pages
async def root_page():
await ui.context.client.connected(30.0) # to ensure storage.tab and storage.client availability
# TODO Timeout should be configurable
request = context.client.request
initial_url = request.url.path
query = request.url.query
Expand Down
27 changes: 27 additions & 0 deletions website/documentation/content/outlet_documentation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from nicegui import ui

from . import doc


@doc.demo(ui.outlet)
def main_demo() -> None:
@ui.outlet('/spa')
def spa_app_outlet():
with ui.header() as header:
ui.label('Simple Single Page App').classes('text-2xl')
yield {'header': header}
ui.label('A footer added to each view.')

@spa_app_outlet.view('/', title="Home")
def spa_index(header):
ui.label('This is the home page of the Single Page App.')
ui.button('Toggle header', on_click=lambda: header.toggle())
ui.link('Go to other page', other_spa_page)

@spa_app_outlet.view('/other', title="Other Page")
def other_spa_page():
ui.label("Another page sharing the same layout.")
ui.link('Go to index page', spa_index)
ui.button("Navigate back", on_click=lambda: ui.navigate.back())

ui.link('Open Single Page App', "/spa")
2 changes: 2 additions & 0 deletions website/documentation/content/section_pages_routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
doc,
download_documentation,
navigate_documentation,
outlet_documentation,
page_documentation,
page_layout_documentation,
page_title_documentation,
Expand Down Expand Up @@ -65,6 +66,7 @@ def icons(icon: str, amount: int = 1):

doc.intro(page_title_documentation)
doc.intro(navigate_documentation)
doc.intro(outlet_documentation)

doc.redirects['open'] = 'navigate#ui_navigate_to_(formerly_ui_open)'
doc.text('ui.open', f'''
Expand Down

0 comments on commit f4f971e

Please sign in to comment.