Skip to content

Commit 09bbc47

Browse files
committed
Merge remote-tracking branch 'origin/single-page-apps' into docs_as_spa
2 parents 698e694 + 0b70892 commit 09bbc47

2 files changed

Lines changed: 60 additions & 7 deletions

File tree

nicegui/elements/sub_pages.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def __init__(
2525
*,
2626
root_path: Optional[str] = None,
2727
data: Optional[Dict[str, Any]] = None,
28+
show_404: bool = True,
2829
) -> None:
2930
"""Create a container for client-side routing within a page.
3031
@@ -37,6 +38,7 @@ def __init__(
3738
:param routes: dictionary mapping path patterns to page builder functions
3839
:param root_path: path prefix to strip from incoming paths (for non-root page mounts)
3940
:param data: arbitrary data passed to all page builder functions
41+
:param show_404: whether to show a 404 error message if the full path could not be consumed; this can be useful for dynamically created nested sub pages
4042
"""
4143
super().__init__()
4244
assert not context.client.shared, (
@@ -51,6 +53,7 @@ def __init__(
5153
self._active_tasks: Set[asyncio.Task] = set()
5254
self._send_update_on_path_change = True
5355
self._current_match: Optional[RouteMatch] = None
56+
self._should_show_404 = show_404
5457
self.show()
5558

5659
def add(self, path: str, page: Callable) -> Self:
@@ -75,17 +78,21 @@ def show(self) -> Optional[RouteMatch]:
7578
self._current_match is not None and \
7679
match_result.path == self._current_match.path and \
7780
not self._required_query_params_changed(match_result):
81+
# NOTE: if the full path could not be consumed, the last sub pages element must handle a possible 404
7882
if not any(el for el in self.descendants() if isinstance(el, SubPages)) and match_result.remaining_path:
79-
self.clear()
80-
self._show_404()
83+
if self._should_show_404:
84+
self.clear()
85+
with self:
86+
self._show_404()
8187
return None
8288
self._scroll_to_fragment(match_result.fragment)
8389
return match_result
8490
self._cancel_active_tasks()
8591
self.clear()
8692
with self:
8793
if match_result is None:
88-
self._show_404()
94+
if self._should_show_404:
95+
self._show_404()
8996
return None
9097
self._send_update_on_path_change = False
9198
self._current_match = match_result
@@ -102,11 +109,14 @@ def _show_page(self, match: RouteMatch) -> bool:
102109
self.clear() # NOTE: we do not want to show partial content created by the builder before the exception was raised
103110
self._show_error(e)
104111
return True
105-
# NOTE: if the full path could not be consumed, the leaf-sub pages element must handle a possible 404
112+
# NOTE: if the full path could not be consumed, the deepest sub pages element must handle a possible 404
106113
has_children = any(el for el in self.descendants() if isinstance(el, SubPages))
107114
if match.remaining_path and not has_children:
108-
self.clear()
109-
self._show_404()
115+
if self._should_show_404:
116+
self.clear()
117+
self._show_404()
118+
if asyncio.iscoroutine(result):
119+
result.close()
110120
return False
111121

112122
self._scroll_to_fragment(match.fragment)
@@ -147,7 +157,6 @@ def _find_matching_path(self) -> Optional[RouteMatch]:
147157
match.remaining_path = urlparse(relative_path).path.rstrip('/')[len(match.path):]
148158
break
149159
segments.pop()
150-
151160
return match
152161

153162
def _match_route(self, path: str) -> Optional[RouteMatch]:

tests/test_sub_pages.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,18 @@ async def content():
248248
screen.should_not_contain('home2')
249249
screen.should_not_contain('some content')
250250

251+
screen.open('/content')
252+
screen.should_contain('some content')
253+
screen.should_contain('home1')
254+
screen.should_not_contain('home2')
255+
screen.should_not_contain('404: sub page')
256+
257+
screen.open('/bad_path')
258+
screen.should_contain('404: sub page /bad_path not found')
259+
screen.should_contain('home1')
260+
screen.should_not_contain('home2')
261+
screen.should_not_contain('some content')
262+
251263

252264
def test_async_nested_sub_pages(screen: Screen):
253265
calls = {
@@ -988,3 +1000,35 @@ async def async_exception():
9881000
screen.should_contain(f'500: {msg_content}')
9891001
screen.assert_py_logger('ERROR', msg_content)
9901002
screen.should_not_contain('content before exception')
1003+
1004+
1005+
def test_disabling_404(screen: Screen):
1006+
1007+
@ui.page('/')
1008+
@ui.page('/{_:path}')
1009+
def index():
1010+
ui.link('Link to 404', '/main/404')
1011+
ui.button('Button to 404', on_click=lambda: ui.navigate.to('/main/404'))
1012+
ui.sub_pages({
1013+
'/main': main,
1014+
}, show_404=False)
1015+
1016+
def main():
1017+
ui.label('main page')
1018+
1019+
screen.open('/main')
1020+
screen.should_contain('main page')
1021+
screen.click('Link to 404') # open by link
1022+
screen.should_not_contain('404: sub page /404 not found')
1023+
screen.should_contain('main page')
1024+
1025+
screen.open('/bad_path')
1026+
screen.should_not_contain('not found')
1027+
1028+
screen.open('/main/bad_path') # direct access
1029+
screen.should_not_contain('not found')
1030+
screen.should_contain('main page')
1031+
1032+
screen.click('Button to 404') # open via navigate.to
1033+
screen.should_not_contain('not found')
1034+
screen.should_contain('main page')

0 commit comments

Comments
 (0)