Comments (12)
Or maybe just call on_connect
only after the websocket is connected and thus the page is fully functional. Or leave on_connect
as is and provide another handler on_websocket_connect
...
from nicegui.
page_stack
and view_stack
They are used internally to keep track of which context is currently open and, thus, where to add an element. If you write
with ui.card():
ui.label('A')
with ui.row():
ui.label('B')
ui.label('C')
ui.label('D')
first a ui.card
is added to the view_stack
. Therefore, the label "A" is added to this card. After entering ui.row
, the next two labels are added to the new top element, i.e. the row. After leaving and popping the row again, label "D" is added to the card.
The page_stack
behaves similarly.
Changing the main page
NiceGUI is intended to be easy to start with. So a three line hello world
from nicegui import ui
ui.label('Hello world!')
ui.run()
should simply work. This implies quite a lot (yes, we know, explicit is better than implicit...), e.g. how the server is started and that there is a main page at route "/". This page is automatically pushed to the page_stack
and remains there. If an element is not explicitly added to a different page, it is added to "/".
Changing the main page is currently a bit tricky. You can get a reference from the first item on the page_stack
:
from nicegui.globals import page_stack
main_page = page_stack[0]
main_page.dark = True
with main_page:
ui.label('Hi!')
On the other hand it should also be ok to create a new page with route "/". Yes, then there are two routes with path "/", but since new pages insert their routes before others, it should work.
with ui.page('/', dark=True):
ui.label('Hi!')
It also might be useful to define a MainPage
class:
class MainPage(ui.page):
def __init__(self):
super().__init__('/', dark=True)
with self:
ui.label('Hi!')
MainPage()
I'll think about whether NiceGUI should automatically remove routes that are overwritten by new pages. The implementation, however, could be challenging, since we would also need to replace the page in the page_stack
...
from nicegui.
It appears this problem affects continuously run tasks too. If they obtain a value after the page is loaded, but before the websocket is connected, then this value is not displayed in the UI too. And it won't be shown until it changes again - NiceGUI is smart enough to skip unnecessary websocket messages when the value is unchanged.
from nicegui.
Your question might be related to the breaking change in version 0.8. Before, we made use of the underlying JustPy framework to automatically update the UI after event handlers (if not actively prevented with a return False
at the end). Now NiceGUI still updates most elements automatically (e.g. a label is updated after setting its text property), but some updates you might have to trigger explicitly yourself. Can you give more details about what should happen in the UI? Or maybe you can boil it down to a minimum example code?
The following example shows the current date and time after a new client has connected. Although there's a delay of 1 second (and the page is updated and rendered), setting label.text
triggers an update for this UI element. Its state is sent to the client which updates the UI in the browser.
import asyncio
from datetime import datetime
from nicegui import ui
async def connect():
await asyncio.sleep(1.0)
label.text = datetime.now().isoformat()
with ui.page('/page', on_connect=connect):
label = ui.label()
ui.run()
But probably you're aware of all that and the question is deeper: Is it possible that an update does not reach the client because the connection is not yet established? As far as I understand, the complete state of a page is held on the server (in Python world) for new clients. When they connect, they get the complete state of that page. I don't see how they could miss an update.
In your example the tasks are even only started after a new connection is created. So how should this connection not be ready when the MTTQ response updates the UI?
You write "but if the response comes between the page has been loaded, but before the websocket connection is up, then the component update is lost." - What do you mean with "loaded"? Doesn't the client "load" the page after establishing a websocket connection?
from nicegui.
In your example the tasks are even only started after a new connection is created. So how should this connection not be ready when the MTTQ response updates the UI?
There are actually two connections: HTML connection and websocket connection. The on_connect
handler is fired when the HTML connection is established, but obviously before the websocket connection, because the websocket is created by the page's javascript.
You write "but if the response comes between the page has been loaded, but before the websocket connection is up, then the component update is lost." - What do you mean with "loaded"? Doesn't the client "load" the page after establishing a websocket connection?
In my understanding, on_connect
handler is called when the browser requests the page. This means that the websocket connection is not yet established, because it needs the page to be fully loaded in the browser, including javascript, because websocket connection is started by javascript.
So, as I see it, the process is as follows:
- We open new tab in the browser and open the NiceGUI webpage in it.
- The browser requests the page from NiceGUI server.
on_connect
is fired.- In my example, async tasks are started.
- NiceGUI prepares HTML file with the initial components state and sends it to the browser.
- Browser starts executing the javascript on the webpage and starts websocket connection.
What would happen if the MQTT response that updates the UI arrives after 5., but before 6. is completed?
Your example with current date and time is slightly different - your on_connect
handler await
s for 1 second. NiceGUI, in turn, await
s for the on_connect
to finish before returning from _route_function
, thus sending HTML code.
In my example, the new tasks are started, so that on_connect
returns immediately. These tasks will kick in when there's another await
down the road, probably when the HTML response is streamed to the browser.
from nicegui.
Oh, I see! on_connect
is called as part of the routing generating the HTML page, "way" before the socket connection is created. You're absolutely right: Changes to the UI that happen after on_connect
returns and before the socket is connected do not reach the client.
Here is a reproduction:
import asyncio
from nicegui import ui
async def on_connect():
label.text = 'loading...'
asyncio.create_task(takes_a_while())
async def takes_a_while():
await asyncio.sleep(0.1)
label.text = 'done'
with ui.page('/page', on_connect=on_connect):
label = ui.label()
ui.run()
With a delay of 0.1s the text "done" never appears. It's the same when removing the delay completely. But a delay of 1.0s works on my machine.
I have to think about if and how we could fix this problem. Maybe we can somehow keep track of UI updates on pages that are still connecting. These updates should be delivered once the socket is open.
As a workaround I would add a delayed UI update to make sure there is an update after the socket connected. This might be a component update (e.g. label.update()
) or even a whole page update (await label.page.update()
).
async def takes_a_while():
await asyncio.sleep(0.1)
label.text = 'done'
await asyncio.sleep(1.0)
label.update()
from nicegui.
Ok, in commit ea05fc6 I introduced an on_page_ready
argument for ui.page
that is called when the websocket is connected. The name is borrowed from the corresponding JustPy event.
Using on_page_ready
the reproduction from above is working as expected. The task is only started after the websocket is fully connected and the changing label text (from "loading..." to "done") is correctly synchronized with the client.
import asyncio
from nicegui import ui
async def on_page_ready():
label.text = 'loading...'
asyncio.create_task(takes_a_while())
async def takes_a_while():
await asyncio.sleep(0.1)
label.text = 'done'
with ui.page('/page', on_page_ready=on_page_ready):
label = ui.label()
ui.run()
from nicegui.
@falkoschindler I am trying to wrap my head around the design of nicegui but don't quite get it (like the questions in #72). Maybe you could point to some documentation of how things work out here?
The example above assumes a global variable label
, otherwise the update methods wouldn't work.
How are you supposed to change state of a component such as the label in your example above for any given larger UI?
Where are the references/names actually "stored"?
I tried to grab them from page_stack[0].instances[1].view.components
but then updates to a component need to be triggered by an await component.update()
where update()
is actually a synchronous method, but still needs to be awaited!?
from nicegui.
Maybe you could point to some documentation of how things work out here?
You've probably seen nicegui.io. That's the only documentation at the moment. More detailed explanations can be found in GitHub issues and discussions. I probably should add some links in a Q&A section.
How are you supposed to change state of a component such as the label in your example above for any given larger UI?
A function like ui.label()
is actually a constructor of a NiceGUI element, which registers itself with the UI. You can store the resulting object wherever you want for later reference. Here is, for example, a "WifiButton" from the library RoSys: https://github.com/zauberzeug/rosys/blob/79c2c1dd8fabd0086fa4d4231f5bd6127291006a/rosys/system/wifi_button_.py#L38. It derives from ui.button
, sets the on_click
event handler and some props
, defines a ui.dialog
and creates a ui.timer
to regularly update itself and its child elements. An explicit UI update is usually not needed. This way you can encapsulate groups of UI elements into classes and use them in higher-level code.
Where are the references/names actually "stored"?
NiceGUI itself doesn't explicitly store UI elements, but only creates JustPy views. If you need the elements, you should hold them yourself as mentioned above. Usually you shouldn't need to tap into nicegui.globals like page_stack
. These are primarily for internal use.
from nicegui.
@hroemer thanks for your reply, the WifiButton demo really is great.
Though I am still not getting a combined sample working, including on_page_ready
.
sample_page.py:
import asyncio
from nicegui import ui
class SamplePage(ui.page):
def __init__(self) -> None:
self.label = ui.label('Any label')
self.label.update()
row = ui.row()
ui.button('+', on_click=lambda: self.add_elements(row))
with ui.card():
with ui.row():
ui.label('Hello world!')
super().__init__(route="/page", title="repr page", on_page_ready=self.on_page_ready)
def add_elements(self, container):
with container:
result = 'something'
ui.label(result)
async def on_page_ready(self):
self.label.text = 'loading...'
asyncio.create_task(self.takes_a_while())
async def takes_a_while(self):
await asyncio.sleep(1.1)
self.label.text = 'done'
main.py
#!/usr/bin/env python3
from jpy.ng.sample_page import SamplePage
ui = SamplePage()
ui.run(reload=True,
host='127.0.0.1',
port=8000,
dark=True
)
Accessing /page
opens a blank page (#72), even though the on_page_ready
event properly fires. Accessing /
elements defined in __init__
are rendered. If I change the route to /
I also get a blank page.
If I change the run
-method to reload=False
I get an AttributeError: 'SamplePage' object has no attribute 'run'
error!?
I would really like to hook up to the on_page_ready
in a composed way like the WifiButton example. Is this feasible in any way?
from nicegui.
@hroemer Let me see...
-
The issue with setting
reload
toTrue
orFalse
is because yourui
is aPage
object, not aUi
. Your main.py should look more like this:from nicegui import ui from sample_page import SamplePage SamplePage() ui.run(reload=True, host='127.0.0.1', port=8000, dark=True)
In the original code NiceGUI's pre-evaluation of
ui.run
(in order to know how to start the uvicorn server while importingui
) is working, although actually callingui.run
does not work. Long story short:ui
should be theUi
object imported fromnicegui
.The instantiated object of type
SamplePage
isn't needed anymore, since it already registered itself with the UI as a side-effect. You can follow the style of NiceGUI and write it like a function and like the other UI elements:... from sample_page import SamplePage as sample_page sample_page() ...
-
The initializer of
SamplePage
also needs some corrections:def __init__(self) -> None: # call base initializer first: super().__init__(route='/page', title='repr page', on_page_ready=self.on_page_ready) # now work within the `self` context: with self: self.label = ui.label('Any label') # calling `update()` is not necessary row = ui.row() ui.button('+', on_click=lambda: self.add_elements(row)) with ui.card(): with ui.row(): ui.label('Hello world!')
The rest should be working as expected.
Well well, NiceGUI definitely needs more documentation about building such custom elements or pages...
from nicegui.
@falkoschindler Thank you for the clarification, using the context managers is obviously the key here. The example is now working as expected.
I still don't understand the (internal) use of the page and view stacks, though. The page is added to the stacks on
enter and removed on exit, thus when __init__()
is done in the sample page example:
def __enter__(self):
page_stack.append(self)
view_stack.append(self.view)
return self
def __exit__(self, *_):
page_stack.pop()
view_stack.pop()
If I change the page route to root path /
two routes actually get registered within the Starlette route instances.
The code still runs as expected, but I fear this might have side effects.
Could you please explain how "pages" are intended to be used here or specifically how to "attach" or use the default
route?
Here's the current code for reference:
sample_page.py:
import asyncio
from nicegui import ui
class SamplePage(ui.page):
def __init__(self) -> None:
# call base initializer first:
super().__init__(route='/', title='sample page', on_page_ready=self.on_page_ready)
# now work within the `self` context:
with self:
self.label = ui.label('Any label')
# calling `update()` is not necessary
row = ui.row()
ui.button('+', on_click=lambda: self.add_elements(row))
with ui.card():
with ui.row():
ui.label('Hello world!')
def add_elements(self, container):
with container:
result = 'something'
ui.label(result)
async def on_page_ready(self):
self.label.text = 'loading...'
asyncio.create_task(self.takes_a_while())
async def takes_a_while(self):
await asyncio.sleep(1.1)
self.label.text = 'done'
main.py:
#!/usr/bin/env python3
from nicegui import ui
from sample_page import SamplePage as sample_page
sample_page()
ui.run(reload=True,
host='127.0.0.1',
port=8000,
dark=True
)
from nicegui.
Related Issues (20)
- Text in input and select components is not aligned to each other HOT 5
- ui.input validation will remain space after it's value was corrected HOT 3
- Select fails to change value on first try. HOT 6
- `run_method` and `getElement` return inconsistent types of elements HOT 1
- Using `copy.deepcopy` on storage causes data loss. HOT 4
- Problem for passing parameter to handler function for dynamic added `ul.html` , `ui.botton`, maybe others. HOT 2
- KeyError in `ui.log` after client disconnected HOT 4
- AG Grids inside hidden tabs don't get mounted HOT 4
- pyinstaller and cpu_bound issue HOT 6
- Leaflet: map flickers between centers after flyTo HOT 8
- Change to MS-Windows style windows is not possible? HOT 2
- 2-way bindings backward direction does not work anymore HOT 1
- Not able to clear input anymore HOT 1
- Accessibility issue with hidden connection warning HOT 3
- Autocomplete for `ui.input` not working on mobile HOT 1
- Need Assistance Calling the Search Filter Function Inside the Search Template in NiceGUI Table
- NiceGUI uses 100% of a CPU core when running inside a container HOT 1
- [Pycharm Problem] Raise asyncio error when reload HOT 1
- local_file_picker Example error in version 1.4.25: HOT 1
- custom vue.js component not updated after modification HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from nicegui.