7. Daemon Views
Closing the browser that displays a Lona view will result in the view getting stopped on the server as well. When two browsers open the same URL at the same time, two view instances get started. This emulates the behavior of most other web frameworks.
Lona views run entirely on the server, thread-safe, and self-contained with all state in them. When View.is_daemon is set to True, a view can run in the background and be synchronized and displayed in multiple browsers at the same time. That also makes long running views possible, where long running tasks can be started and later revisited.
Daemonized views are only available to the same user that started the view.
Note
Daemonized views are not meant to be used to create multi-user views. They are meant to create single-user views that are long running and/or should be visible in multiple browsers at the same time.
If you want to create multi-user views, use channels instead.
Short Running View
This example implements a short running view, implementing a simple counter and a text area. By deamonizing it, its content gets synchronized between all browser tabs of the same user.
This style of view is called "short running" because its handle_request() method returns immediately.
Note
Daemonized views get removed immediately from the server when their LonaView.handle_request() returns. Since version 1.11, you can disable this and keep the daemonized view running by setting LonaView.STOP_DAEMON_WHEN_VIEW_FINISHES to False.
This behavior needs to be manually enabled due to backward compatibility, but will be the default from Lona 2.0 onwards.
from lona_picocss.html import InlineButton, TextArea, Span, HTML, Div, H1, Br
from lona_picocss import install_picocss
from lona import View, App
app = App(__file__)
install_picocss(app, debug=True)
@app.route('/')
class Index(View):
STOP_DAEMON_WHEN_VIEW_FINISHES = False
def handle_button_click(self, input_event):
with self.html.lock:
span = self.html.query_selector('span#counter')
span.set_text(int(span.get_text()) + 1)
def handle_request(self, request):
# daemonize view
self.is_daemon = True
self.html = HTML(
H1('Daemonized View'),
Div(
'Counter: ',
Span('10', _id='counter'),
' ',
InlineButton(
'Increment',
handle_click=self.handle_button_click,
),
Br(),
Br(),
),
TextArea(placeholder='TextArea')
)
return self.html
if __name__ == '__main__':
app.run()
Long Running View
This example implements a more complex counter, that counts on its own in the background and can be canceled using a pop-up. This style of view is called "long running" because its handle_request() method runs as long as the view runs.
This is handy for writing wizards or processing huge amounts of data.
Note
This style of view blocks one Python thread for its entire runtime.
from lona_picocss.html import (
InlineButton,
NumberInput,
Progress,
Modal,
Label,
HTML,
Grid,
Pre,
H3,
H1,
P,
)
from lona_picocss import install_picocss
from lona import View, App
app = App(__file__)
install_picocss(app, debug=True)
@app.route('/')
class Index(View):
def open_modal(self, input_event):
with self.html.lock:
self.modal.get_body().nodes = [
H3('Stop Counter'),
P('Are you sure you want to stop the counter?'),
]
self.modal.get_footer().nodes = [
InlineButton(
'Cancel',
secondary=True,
handle_click=lambda i: self.modal.close(),
),
InlineButton(
'Stop',
handle_click=self.stop_counter,
),
]
self.modal.open()
def stop_counter(self, input_event):
self.modal.close()
self.running = False
def handle_request(self, request):
# daemonize view
self.is_daemon = True
# wait for start
self.limit = NumberInput(value=10)
self.html = HTML(
H1('Counter'),
Grid(
Label(
'Limit',
self.limit,
),
InlineButton('Start'),
),
)
self.await_click(html=self.html)
# count to limit
self.running = True
self.modal = Modal()
progress = Progress(value=0)
pre = Pre(
style={
'height': f'{int(self.limit.value) + 4}em',
},
)
self.html = HTML(
H1('Counter'),
progress,
pre,
InlineButton(
'Stop',
handle_click=self.open_modal,
style={
'float': 'right',
},
),
self.modal,
)
for i in range(1, int(self.limit.value) + 1):
# check if view should stop
if not self.running:
pre.write_line('view stopped')
self.show(self.html)
return
progress.value = 100 / int(self.limit.value) * i
pre.write_line(f'Counting to {i}')
self.show(self.html)
self.sleep(1)
# finish
pre.write_line('finish')
return self.html
if __name__ == '__main__':
app.run()