Skip to content

Commit

Permalink
Removed method decoration and replaced it with additional page_config…
Browse files Browse the repository at this point in the history
…s registry in Client.

General clean-up
Added titles to sample app
Added docu to SPA
  • Loading branch information
Michael Ikemann committed Apr 3, 2024
1 parent 01c3ce8 commit 4b89fb3
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 19 deletions.
6 changes: 3 additions & 3 deletions examples/session_storage/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from nicegui.single_page import SinglePageRouter


@page('/')
@page('/', title="Welcome!")
def index():
username = app.storage.session.get('username', '')
if username == '': # redirect to login page
Expand All @@ -19,7 +19,7 @@ def index():
ui.link('Logout', '/logout')


@page('/login')
@page('/login', title="Login")
def login_page():
def login():
fake_pw_dict = {'user1': 'pw1',
Expand All @@ -45,7 +45,7 @@ def handle_login():
ui.html("<small>Psst... try user1/pw1, user2/pw2, user3/pw3</small>")


@page('/logout')
@page('/logout', title="Logout")
def logout():
app.storage.session['username'] = ''
app.storage.session['password'] = ''
Expand Down
3 changes: 3 additions & 0 deletions nicegui/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ class Client:
page_routes: Dict[Callable[..., Any], str] = {}
"""Maps page builders to their routes."""

page_configs: Dict[Callable[..., Any], "page"] = {}
"""Maps page builders to their page configuration."""

single_page_routes: Dict[str, Any] = {}
"""Maps paths to the associated single page routers."""

Expand Down
2 changes: 1 addition & 1 deletion nicegui/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,5 @@ async def wait_for_result() -> None:

self.api_router.get(self._path, **self.kwargs)(decorated)
Client.page_routes[func] = self.path
func.__setattr__("__ng_page", self)
Client.page_configs[func] = self
return func
47 changes: 32 additions & 15 deletions nicegui/single_page.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,50 @@
import asyncio
import inspect
from typing import Callable, Dict, Union

from fastapi.routing import APIRoute

from nicegui import background_tasks, helpers, ui, core, Client, app
from nicegui.app import AppConfig


class RouterFrame(ui.element, component='single_page.js'):
"""The RouterFrame is a special element which is used by the SinglePageRouter to exchange the content of the
current page with the content of the new page. It serves as container and overrides the browser's history
management to prevent the browser from reloading the whole page."""

def __init__(self, base_path: str):
"""
:param base_path: The base path of the single page router which shall be tracked (e.g. when clicking on links)
"""
super().__init__()
self._props["base_path"] = base_path


class SinglePageRouter:
"""The SinglePageRouter allows the development of a Single Page Application (SPA) which maintains a
persistent connection to the server and only updates the content of the page instead of reloading the whole page.
This enables the development of complex web applications with large amounts of per-user (per browser tab) data
which is kept alive for the duration of the connection."""

def __init__(self, path: str, **kwargs) -> None:
"""
:param path: the base path of the single page router.
"""
super().__init__()

self.routes: Dict[str, Callable] = {}
# async access lock
self.base_path = path
self.find_api_routes()
self._find_api_routes()

@ui.page(path, **kwargs)
@ui.page(f'{path}' + '{_:path}', **kwargs) # all other pages
async def root_page(client: Client):
await client.connected()
if app.storage.session.get('__pageContent', None) is None:
content: Union[ui.element, None] = RouterFrame(self.base_path).on('open', lambda e: self.open(e.args))
app.storage.session['__pageContent'] = content

def find_api_routes(self):
def _find_api_routes(self):
"""Find all API routes already defined via the @page decorator, remove them and redirect them to the
single page router"""
page_routes = set()
for key, route in Client.page_routes.items():
if (route.startswith(self.base_path) and
Expand All @@ -46,14 +58,19 @@ def find_api_routes(self):
if route.path in page_routes:
core.app.routes.remove(route)

def add(self, path: str):
def decorator(func: Callable):
self.routes[path] = func
return func
def add(self, path: str, builder: Callable) -> None:
"""Add a new route to the single page router
return decorator
:param path: the path of the route
:param builder: the builder function"""
self.routes[path] = builder

def open(self, target: Union[Callable, str], server_side=False) -> None:
"""Open a new page in the browser by exchanging the content of the root page's slot element
:param target: the target route or builder function
:param server_side: Defines if the call is made from the server side and should be pushed to the browser
history"""
if isinstance(target, Callable):
target = {v: k for k, v in self.routes.items()}[target]
builder = target
Expand All @@ -62,9 +79,9 @@ def open(self, target: Union[Callable, str], server_side=False) -> None:
return
builder = self.routes[target]

if "__ng_page" in builder.__dict__:
new_page = builder.__dict__["__ng_page"]
title = new_page.title
page_config = Client.page_configs.get(builder, None)
if page_config is not None: # if page was decorated w/ title, favicon etc.
title = page_config.title
ui.run_javascript(f"document.title = '{title if title is not None else core.app.config.title}'")

if server_side:
Expand Down

0 comments on commit 4b89fb3

Please sign in to comment.