๐ฅ๏ธ Lively Component Systemยถ
Duck now includes the Lively Component Systemโa real-time, component-based system that enables responsive page updates without full reloads.
It leverages WebSockets with msgpack for fast communication and supports navigation to new URLs without full page reloads.
Recommended: Use Duck components over traditional templates. Components are flexible, Pythonic, and allow pluggable, reusable UI elements.
๐ Page Component Exampleยถ
from duck.html.components.page import Page
from duck.html.components.button import Button
class HomePage(Page):
def on_create(self):
super().on_create()
self.set_title("Home - MySite")
self.set_description("Welcome to MySite, the premier platform...")
self.set_favicon("/static/favicon.ico")
self.set_opengraph(
title="Home - MySite",
description="Welcome to MySite, the premier platform...",
url="https://mysite.com",
image="https://mysite.com/og-image.png",
type="website",
site_name="MySite"
)
self.set_twitter_card(card="summary_large_image", title="Home - MySite")
self.set_json_ld({
"@context": "https://schema.org",
"@type": "WebSite",
"url": "https://mysite.com",
"name": "MySite",
"description": "Welcome to MySite, the premier platform..."
})
self.add_to_body(Button(text="Hello world"))
Organizing pages this way isolates page-specific logic in dedicated classes, making code easier to maintain, extend, and debug.
๐ฑ๏ธ Component Eventsยถ
Lively components allow binding Python handlers to events like button clicksโno JS needed.
from duck.shortcuts import to_response
from duck.html.components.button import Button
from duck.html.components.page import Page
from duck.html.core.websocket import LivelyWebSocketView
from duck.html.core.exceptions import JSExecutionError, JSExecutionTimedOut
async def on_click(btn: Button, event: str, value: Any, websocket: LivelyWebSocketView):
"""
Button onclick event.
Args:
btn (Button): Button component.
event (str): Event name.
value (str): Current button value.
websocket (LivelyWebSocketView): Active WebSocket.
"""
btn.bg_color = "red" if btn.bg_color != "red" else "green"
try:
await websocket.execute_js(
code='alert(`Javascript execution success`);',
timeout=2,
wait_for_result=True
)
except (JSExecutionTimedOut, JSExecutionError):
pass
def home(request):
page = Page(request)
btn = Button(id="some-id", text="Hello world", bg_color="green", color="white")
page.add_to_body(btn)
btn.bind("click", on_click)
return to_response(page) # Or just return page if you don't want control over response.
Use
update_targetswhen multiple components must update on an event to avoid redundant updates.
Document-specific eventsยถ
These are events bound directly to the document rather than HTML elements. Duck provides a way to bind to these events
but itโs only available on Page component instances.
Here is an example:
# views.py
from duck.html.components.page import Page
def home(request):
page = Page(request)
def on_navigation(page, *_):
print(f"Navigated to page {page}")
def on_page_load(page, *_):
print(f"Page loaded, {page}")
# Bind to the document.
page.document_bind("DuckNavigated", on_navigation, update_self=False)
page.document_bind("DOMContentLoaded", on_page_load, update_self=False)
return page
โฑ๏ธ Pre-rendering Componentsยถ
Pre-rendering caches component outputs for faster loading.
Example:
from duck.html.components.page import Page
from duck.shortcuts import to_response
def home(request):
page = Page(request=request)
background_thread.submit_task(
lambda: page.pre_render(deep_traversal=True, reverse_traversal=True)
)
return to_response(page)
Counter Appยถ
Include the built-in counter blueprint to test:
BLUEPRINTS = [
"duck.etc.apps.counterapp.blueprint.CounterApp",
]
Visit /counterapp after adding it.
๐ Notesยถ
Component responses maximize the benefits of Lively Component System.
Fast navigation works best with component responses in views.
Components in Templatesยถ
Components can be used in Jinja2 and Django templates.
Jinja2 Example:
{{ Button(
id="btn",
text="Hello world",
)
}}
Django Example:
{% Button %}
id="btn",
text="Hello world",
{% endButton %}
Only add components to
TEMPLATE_HTML_COMPONENTSin settings.py for template usage.
๐๏ธ Custom Componentsยถ
from duck.html.components import InnerComponent
from duck.html.components.button import Button
from duck.shortcuts import to_response
class MyComponent(InnerComponent):
def get_element(self):
return "div"
def on_create(self):
super().on_create()
self.add_child(Button(text="Hi there"))
def home(request):
comp = MyComponent(request=request)
return to_response(comp)
๐ Component Extensionsยถ
Extensions enhance component functionality.
from duck.html.components.button import Button
from duck.html.components.extensions import Extension
class MyExtension(Extension):
def on_create(self):
super().on_create()
self.style["background-color"] = "red"
class MyButton(MyExtension, Button):
pass
btn = MyButton() # Button with background-color "red"
Notes:
By default, all Lively components comes up with 2 builtin component extensions as follows:
BasicExtension: This is the basic extension which enables setting attributes like
color,bg_color& more to actually alter the componentstyle.StyleCompatibilityExtension: This enables compatibility of style properties like setting
backdrop-filterwill also set the-webkit-backdrop-filterand other compatibility style properties.
๐ฆ Predefined Componentsยถ
All predefined components are available in duck.html.components and in default SETTINGS['TEMPLATE_HTML_COMPONENTS'].
Use these components to rapidly build responsive, interactive UIs in Duck.
Force Updates on Lively Componentsยถ
It is possible to modify values set with Javascript with the use of Force updates.
The following example will showcase this technique:
from duck.html.components import ForceUpdate
from duck.html.components.button import Button
from duck.html.components.script import Script
def on_btn_click(btn, *_):
# Return a force update to reset btn text to the initial text set on btn
# First argument to ForceUpdate is the component and second is list of updates.
# List of updates are "text"/"inner_html", "props", "style" and "all".
return ForceUpdate(btn, ["text"])
def home(request):
btn = Button(text="Click me", id="btn")
# Add some Javascript
script = Script(
inner_html="""
function btnClick() {
const btn = document.getElementId(`btn`);
btn.textContent = "Clicking..";
}
"""
)
# Add script to button
btn.add_child(script)
# Add a javascript callback to click event.
# This always gets called first before Duck event callback.'
btn.props["onclick"] = "btnClick()"
# Bind click event to python callable.
btn.bind("click", on_btn_click, update_self=False)
# Return btn, will be converted to response by Duck.
return btn
Handling Formsยถ
Duck has got an easy mechanism you can use to easily handle form data.
Example:ยถ
from duck.html.components.form import Form
from duck.html.components.input import Input
from duck.html.components.fileinput import FileInput
def on_form_submit(form, event, value: dict, _):
name = value.get("name")
email = value.get("email")
file_metadata: dict = value.get("file") # Only file metadata will be resolved.
# Do something with the form data, maybe validation
def home(request):
form = Form(
fields=[
Input(type="text", name="name", placeholder="Name"),
Input(type="email", name="email", placeholder="email"),
FileInput(name="file"),
],
)
# Bind an event to form submission
form.bind("submit", on_form_submit)
# Return form or any parent
return form
From the above example, whenever submit event is bound to a component,
the JS method preventDefault() is called to avoid page reload.
Also, only file metadata (size, type & name) is received on submit event because Lively is not designed
to handle file uploads. You need to manually call internal apiโs or views for file uploads and update the UI
if complete. One case you can do this is that you may execute JS for doing an AJAX request and update
the UI once the response is received.
Component Lifecycleยถ
Whenever each component is created or added to the component tree, the following methods are executed:
on_root_finalized:ยถ
This will be called whenever a root component is finalized, meaning it is never going to change. Use the for doing staff on root components e.g.,
using document_bind on Page components.
Args:
root: This is the root component.
on_parentยถ
Called whenever a child component is added to a parent component.
Args:
parent: The parent component.
Other points to Noteยถ
Duck only keeps track of
props(attributes) andstyle(style attributes) that are initially set using Duck. This means that Duck will not touch any prop or style attributes set soley using Javascript and are never declared on Lively components.The use of
LiveResultis only applicable to componentinner_htmlortext.The
valueargument parsed to a Duck event handler is variable and can be any datatype depending on the event.Component traversal can be done using
root,parentorchildrenproperties.JS execution is done asynchronously so all code that is parsed in
LivelyWebSocketView.execute_jsorget_js_resultis awaited by default.DOM mutation/updates using Javascript like moving, adding or removing components without using Lively will cause issues and by default, this will be flagged in the browser.