domsync is a library for building responsive web UIs in Python. A DOM document containing the whole UI is built and updated on the Python server side, changes to this DOM are synchronised efficiently to the Browser. Events that happen on the Browser-side trigger callbacks on the Python-side. This allows you to keep what the user sees in your Python process, close to your existing Python logic, eliminating the need for creating and maintaining a separate Javascript client application and building an API interface to communicate with the client.
The syntax of domsync closely follows the core Javascript syntax for manipulating a DOM document:
we got getElementById
, createElement
, appendChild
, setAttribute
, addEventListener
, and so on. Every change to the Python domsync document
generates Javascript code which is almost equivalent to the Python domsync call, this allows users to clearly understand and control what
is happening to the DOM document on the Browser-side.
Install domsync with:
pip install domsync
This Python domsync app shows the current time:
import asyncio
from datetime import datetime
from domsync.domsync_server import DomsyncServer
async def connection_handler(server, client):
"""
connection_handler is called every time a client connects to the server
:param server: is the DomsyncServer instance
:param client: is a websocket client connection instance
"""
# get the client's domsync Document
document = server.get_document(client)
# add a div to the root element
root_element = document.getRootElement()
div_static = document.createElement('div', innerText = 'The current time is:')
root_element.appendChild(div_static)
div_time = document.createElement('div')
root_element.appendChild(div_time)
while True:
# update the text of div_time to the current time
div_time.innerText = datetime.utcnow().isoformat()
# send updates to the client
await server.flush(client)
# wait a bit
await asyncio.sleep(0.1)
async def main():
# start a domsync server on localhost port 8888
await DomsyncServer(connection_handler, 'localhost', 8888).serve()
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
asyncio.get_event_loop().run_forever()
Let's take a look at what happens here.
await DomsyncServer(connection_handler, 'localhost', 8888).serve()
starts a domsync server which is essentially a websocket server with a domsyncDocument
instance for each connected client.async def connection_handler(server, client)
is the handler that runs when a new client connects to the server. The arguments of this function are theDomsyncServer
instance and the websocket client connection instance.doc = server.get_document(client)
gets the domsyncDocument
associated with the client which contains the DOM. Each client has it's separateDocument
that can be manipulated separately.root_element = document.getElementById(document.getRootId())
gets the root element of theDocument
which corresponds to the<div id='domsync_root_id'></div>
element in the client-side HTML.
div_static = document.createElement('div', innerText = 'The current time is:')
creates a newdiv
element in the document with the given innerText.
root_element.appendChild(div_static)
appendsdiv_static
underroot_element
as a child.
div_time = document.createElement('div')
creates anotherdiv
element in the document.
root_element.appendChild(div_time)
appendsdiv_time
underroot_element
as a child.
div_time.innerText = datetime.utcnow().isoformat()
updates the innerText ofdiv_time
to the current time.
These operations modify the domsyncDocument
in memory but also generate Javascript code which is saved in an internal buffer of theDocument
. At this point the content of the buffer is this generated Javascript code:var __domsync__ = []; __domsync__["domsync_root_id"] = document.getElementById("domsync_root_id"); __domsync__["__domsync_el_0"] = document.createElement("div");__domsync__["__domsync_el_0"].setAttribute("id","__domsync_el_0"); __domsync__["__domsync_el_0"].innerText = `The current time is:`; __domsync__["domsync_root_id"].appendChild(__domsync__["__domsync_el_0"]); __domsync__["__domsync_el_1"] = document.createElement("div");__domsync__["__domsync_el_1"].setAttribute("id","__domsync_el_1"); __domsync__["domsync_root_id"].appendChild(__domsync__["__domsync_el_1"]); __domsync__["__domsync_el_1"].innerText = `2022-06-18T12:48:10.886967`;
await server.flush(client)
sends the contents of the Javascript buffer to the client where it gets evaluated and as a result the current time appears on the screen.- As the
while
loop progresses, theDocument
is modified and the generated Javascript code is sent to the client continuously. However, domsync is efficient in the sense that it only sends changes for those elements that have actually changed, in this example this is the only line of generated Javascript that is sent by the nextawait server.flush(client)
:__domsync__["__domsync_el_1"].innerText = `2022-06-18T12:48:13.889425`;
This is the generic Browser-side domsync client:
<html>
<!-- domsync will render into this element -->
<body><div id='domsync_root_id'></div></body>
<script type = "text/javascript">
// server -> client: DOM changes are coming from websocket as javascript code and are eval'ed here
socket = new WebSocket("ws://localhost:8888");
socket.onmessage = function(event) { (function(){eval.apply(this, arguments);}(event.data)); };
// client -> server: ws_send is called by event handlers to send event messages to the server
function ws_send(msg) { socket.send(JSON.stringify(msg)); };
</script>
</html>
The client connects to the domsync server running on localhost port 8888 over websocket.
The domsync server sends javascript code containing DOM operations that are evaluated in socket.onmessage
.
The ws_send
function is used as an event callback to send events back to the server.
This example is in examples/example_clock.py
with the client-side html in examples/client.html
.
Read the docs: https://domsync.readthedocs.io/