Basic Concept

Writing HTML

Lona uses a combination of Jinja and Lona nodes to generate HTML.

When writing a single page application, only a small fraction of the generated HTML ever changes. The basic layout, the banner, navigation and footer always stay the same. Therefore this part of a Lona application gets generated using a Jinja template (more information: Lona Frontends).

The part of the application that changes is generated by a Lona View, using Lona nodes, HTML strings or a Jinja template.

Workflow

Lets say you have view that counts to ten:

# views/count_to_ten.py

from lona.html import HTML, H1, P, Div
from lona.view import LonaView


class CountToTen(LonaView):
    def handle_request(self, request):
        counter = Div('0')

        html = HTML(
            H1('Count To Ten'),
            P('Counter: ', counter),
        )

        for i in range(1, 11):
            counter.set_text(i)
            self.show(html)

            self.sleep(1)

and the URL to this view is /count-to-ten/

# routes.py

routes = [
    Route('/count-to-ten/', 'views/count_to_ten.py::CountToTen')
]

When you open http://localhost:8080/count-to-ten/ with your browser, your view gets not started directly, but Lona starts your frontend view. This view is supposed to serve the basic layout of your page (banner, navigation, footer and so on) and the Lona Javascript client.

The Javascript client then opens a Websocket connection to the server and sends the message lona:[1,null,101,["http://localhost:8080/count-to-ten/",null]].

Lona will then start your view and will send the HTML given to show() in a similar message. All Lona messages are JSON encoded, the payload of the message will look like this:

[WIDGET,
 '663758909164540',
 '',
 [[NODE,
   '663758908759614',
   'h1',
   [],
   [],
   {},
   {},
   [[TEXT_NODE, '663758908791192', 'Count To Ten']]],
  [NODE,
   '663758908960048',
   'p',
   [],
   [],
   {},
   {},
   [[TEXT_NODE, '663758908983761', 'Counter: '],
    [NODE,
     '663758909061401',
     'div',
     [],
     [],
     {},
     {},
     [[TEXT_NODE, '663788860494138', '0']]]]]],
  {}]

The encoded HTML gets rendered to browser readable HTML by the Javascript client

<h1 data-lona-node-id="663758908759614">
  Count To Ten
</h1>
<p data-lona-node-id="663758908960048">
  Counter:
  <div data-lona-node-id="663758909061401">
    0
  </div>
</p>

The HTML gets only send entirely once, because the view only updates the div named counter before calling show() again.

Every Lona HTML node has a unique id stored in data-lona-node-id. When a node gets updated Lona sends updates only for that specific node.

Asynchronous Code

Lona is based on asyncio and uses aiohttp as its HTTP server. So Lona is asynchronous internally, but provides an asynchronous API that does not use Pythons async and await syntax.

from lona.html import HTML, H1, Button
from lona import LonaView


class MyLonaView(LonaView):
    def handle_request(self, request):
        html = HTML(
            H1('Click The Button'),
            Button('Click Me'),
        )

        self.show(html)

        # This call blocks until the button is clicked
        input_event = self.await_click()

        self.show(H1('button was clicked'))

Why is that? Asyncio implements cooperative multitasking. That means Asyncio requires every piece of code, running on its ioloop, to be fully compliant.

Lets say Lona would use await for its asynchronous API and you want to write a view that pulls all data from a database and save it again:

from lona.html import HTML, H1, Button
from orm.models import MyModel
from lona import LonaView


class MyLonaView(LonaView):
    async def handle_request(self, request):
        html = HTML(
            H1('Click The Button to update database'),
            Button('Click Me'),
        )

        self.show(html)

        input_event = await self.await_click()

        # pull data from the database and update in the database
        for model in MyModel.objects.all():
            model.update_table()

When the button is clicked the view does not release the ioloop scope until the all database operations are finished. In this time, nothing else can happen. The ioloop is blocked (potential forever). This is no obvious problem for developers that are not that familiar with asyncio, and issues like that first show up in production, in a multi user environment.

You could move all blocking code to a thread (thats what Lona internally does with your whole view) to not block the ioloop, but that splits the view logic in multiple pieces of code, what introduces complexity.

Lona uses multi-threading which allows for self-contained views without the need to implement cascading callbacks. This does not mean you cant use await in your views if you really have to (more information: LonaView.await_sync()

More information on resource management: Resource Management