-
-
Notifications
You must be signed in to change notification settings - Fork 592
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Is it possible to not share the state between multiple browser windows? #6
Comments
This is an interesting requirement. The short answer would be no. NiceGUI is build on JustPy, a framework which uses the python backend as frontend. So there is technically no state per browser tab. All state is kept synchronised between all clients. But we have an idea on how to use the interaction event from one of the browser tabs to trigger an action which is only executed on the certain tab. It's quite tricky but the work had become more intense thanks to your request. |
Thanks for the information about Justpy.
|
Thanks for your input @qiuwei. You are absolutely right. In your code each browser tab creates a new |
The architecture of NiceGUI is build around the concept of shared pages. Otherwise you would need to mange the state of each client separately. As you did by attaching the counter to the div-tag ( #!/usr/bin/env python3
from starlette.websockets import WebSocket
from uuid import uuid4
from nicegui import ui
from nicegui.elements.page import Page
class PrivatePage(Page):
def __init__(self, socket: WebSocket):
route = f'/{str(uuid4())}'
super().__init__(route)
self.count = 0
with self:
self.status = ui.label('0').style('font-size:4em')
with ui.row():
ui.button('increment', on_click=self.increment)
ui.button('CLOSE', on_click=lambda e: ui.open('/', e.socket))
ui.open(route, socket)
def increment(self):
self.count += 1
self.status.set_text(self.count)
ui.button('open private page', on_click=lambda e: PrivatePage(e.socket))
ui.run() As you can see every private page has its own state. Please let us know if this is sufficient to solve your problem. |
I'll close the issue. @qiuwei feel free to contact us any time if you still have questions. |
I've come up with my own solution not requiring additional paths. I'll share it a little later. I didn't test it thoroughly though, but the tabs in one browser share the state while the tabs in different browsers are separate. |
Interesting! We're also thinking about how NiceGUI creates a common page instance for all clients and how we could (optionally) generate a new one for each client. But it's still just an idea. |
My solution: # Creating a PrivatePage for some route will make page instances with different sessions independent.
class PrivatePage(ui.page):
def __init__(self, route: str, setup_ui: Callable, *args, **kwargs):
self.setup_ui, self.args, self.kwargs = setup_ui, args, kwargs
if 'on_disconnect' in kwargs:
del kwargs['on_disconnect']
super().__init__(route, *args, **kwargs)
self.individual_page_viewers: dict[str, int] = {}
self.individual_pages: dict[str, ui.page] = {}
async def on_disconnect(self, websocket: WebSocket):
if 'on_disconnect' in self.kwargs:
disconnect_handler = self.kwargs['on_disconnect']
await disconnect_handler() if is_coroutine(disconnect_handler) else disconnect_handler()
session_id = websocket.cookies['jp_token'].split('.')[0]
self.individual_page_viewers[session_id] -= 1
print(f'Viewer quit, session id {session_id}, total {self.individual_page_viewers[session_id]}')
if self.individual_page_viewers[session_id] == 0:
self.individual_pages.pop(session_id).remove_page() # To reclaim used memory
#TODO: override __enter__ and __exit__ to be able to use it in context manager like ordinary page?
async def _route_function(self, request: Request):
# we spawn additional page instance for each session id
if request.session_id not in self.individual_pages:
self.individual_pages[request.session_id] = ui.page(self.route,
*self.args,
on_disconnect=self.on_disconnect,
**self.kwargs)
jp.Route.instances.pop(0)
self.individual_page_viewers[request.session_id] = 0
self.setup_ui(self.individual_pages[request.session_id])
self.individual_page_viewers[request.session_id] += 1
print(f'New viewer, session_id {request.session_id}, viewers {self.individual_page_viewers[request.session_id]}')
return await self.individual_pages[request.session_id]._route_function(request) I also changed async def on_disconnect(self, websocket=None) -> None:
for disconnect_handler in ([self.disconnect_handler] if self.disconnect_handler else []) + disconnect_handlers:
arg_count = len(inspect.signature(disconnect_handler).parameters)
is_coro = is_coroutine(disconnect_handler)
if arg_count == 1:
await disconnect_handler(websocket) if is_coro else disconnect_handler(websocket)
elif arg_count == 0:
await disconnect_handler() if is_coro else disconnect_handler()
else:
raise ValueError(f'invalid number of arguments (0 or 1 allowed, got {arg_count})')
await super().on_disconnect(websocket) Usage: def login(args):
# you can access the page object as sender.page, etc.
sender = args.sender
socket = args.socket
pass
def setup_login_ui(page: ui.page):
with page:
ui.input('User Name').classes('w-full')
ui.input('Password').classes('w-full').props('type=password')
ui.button('Log in', on_click=login)
login_page = PrivatePage('/login', setup_ui=setup_login_ui)
ui.run() Currently it doesn't allow setting up page right after creation like with ordinary pages, it needs a callback which is invoked once per specific session id. |
Thanks, @me21! I looked it through and it seems to be a very reasonable approach. I'll probably try to bake it more into the original By the way: Providing an optional socket in page.on_disconnect seems very handy and consistent with the other callbacks. I added it to the main branch. 56ff679 |
In the button example,
How can I have separate button counts for different multiple browser windows?
The text was updated successfully, but these errors were encountered: