diff --git a/nicegui/elements/sub_pages.py b/nicegui/elements/sub_pages.py index 86a8d2ddc0..43670ca568 100644 --- a/nicegui/elements/sub_pages.py +++ b/nicegui/elements/sub_pages.py @@ -67,6 +67,14 @@ def add(self, path: str, page: Callable) -> Self: self._show() return self + def refresh(self) -> None: + """Rebuild this sub pages element. + + *Added in version 3.1.0* + """ + self._reset_match() + self._show() + def _show(self) -> None: """Display the page matching the current URL path.""" self._rendered_path = '' diff --git a/nicegui/sub_pages_router.py b/nicegui/sub_pages_router.py index 7e3785e6b1..81a8049919 100644 --- a/nicegui/sub_pages_router.py +++ b/nicegui/sub_pages_router.py @@ -49,6 +49,19 @@ def on_path_changed(self, handler: Callable[[str], None]) -> None: """ self._path_changed_handlers.append(handler) + async def refresh(self) -> None: + """Refresh the currently shown sub pages. + + This will clear and rebuild the current sub page as if navigating to it again. + Useful when you want to update the page content based on changes in data or state. + + *Added in version 3.1.0* + """ + for el in context.client.layout.descendants(): + if isinstance(el, SubPages): + el._reset_match() # pylint: disable=protected-access + await self._handle_open(self.current_path) + async def _handle_open(self, path: str) -> bool: self.current_path = path self.is_initial_request = False diff --git a/tests/test_sub_pages.py b/tests/test_sub_pages.py index c82c375b81..60cb1152fb 100644 --- a/tests/test_sub_pages.py +++ b/tests/test_sub_pages.py @@ -1169,3 +1169,46 @@ def index(): screen.click('Delete') screen.wait(0.5) screen.should_not_contain('main page') + + +def test_refresh_sub_page(screen: Screen): + calls = {'index': 0, 'outer': 0, 'inner_main': 0, 'inner_other': 0} + + @ui.page('/') + @ui.page('/{_:path}') + def index(): + calls['index'] += 1 + sub_pages = ui.sub_pages({'/': outer_page}) + ui.button('Refresh via Router', on_click=ui.context.client.sub_pages_router.refresh) + ui.button('Refresh via SubPages', on_click=sub_pages.refresh) + + def outer_page(): + calls['outer'] += 1 + ui.sub_pages({'/': inner_main, '/other': inner_other}) + ui.link('Go to other', '/other') + + def inner_main(args: PageArguments): + calls['inner_main'] += 1 + ui.button('Refresh inner main', on_click=args.frame.refresh) + + def inner_other(args: PageArguments): + calls['inner_other'] += 1 + ui.button('Refresh inner other', on_click=args.frame.refresh) + + screen.open('/') + assert calls == {'index': 1, 'outer': 1, 'inner_main': 1, 'inner_other': 0} + + screen.click('Refresh inner main') + assert calls == {'index': 1, 'outer': 1, 'inner_main': 2, 'inner_other': 0} + + screen.click('Go to other') + assert calls == {'index': 1, 'outer': 1, 'inner_main': 2, 'inner_other': 1} + + screen.click('Refresh inner other') + assert calls == {'index': 1, 'outer': 1, 'inner_main': 2, 'inner_other': 2} + + screen.click('Refresh via Router') + assert calls == {'index': 1, 'outer': 2, 'inner_main': 2, 'inner_other': 3} + + screen.click('Refresh via SubPages') + assert calls == {'index': 1, 'outer': 3, 'inner_main': 2, 'inner_other': 4}