๐Ÿ–ฅ๏ธ Lively Component Systemยถ

Badge

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_targets when 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

โšก Fast Navigationยถ

  • URL paths returning Component responses allow vdom-diffing for minimal DOM updates.

  • Use duckNavigate for fast page navigation.

  • All links do partial page reload whenever possible. Exempt links from partial page reloading by setting data-no-duck attribute.

  • The default window.open has been altered to use fast navigation whenever possible.

  • Whenever a user want to visit a next page, whenever possible, fast navigation is used. If you want to override this, letโ€™s say you want the user to apply new page headers, you can set fullpage_reload or fullpage_reload_headers (_headers that force a fullpage reload when set on Page's request_) on Page components.


โฑ๏ธ 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_COMPONENTS in 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 component style.

    • StyleCompatibilityExtension: This enables compatibility of style properties like setting backdrop-filter will also set the -webkit-backdrop-filter and 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) and style (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 LiveResult is only applicable to component inner_html or text.

  • The value argument parsed to a Duck event handler is variable and can be any datatype depending on the event.

  • Component traversal can be done using root, parent or children properties.

  • JS execution is done asynchronously so all code that is parsed in LivelyWebSocketView.execute_js or get_js_result is 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.