Source code for duck.html.components.core.force_update

"""
Module containing a class to force update a component, i.e. force generate
a patch on event binding. Very useful for components updated using JS.

**Usage Example:**

```py
# views.py

from duck.html.components.page import Page
from duck.html.components.button import Button

def myview(request):
    def on_button_click(btn, *_):
        # The following will return update the Button text/innerHTML regardless. Even if it was modified through Javascript.
        return ForceUpdate(btn, ["text"]) # Or return a list of ForceUpdates
    
    # Do some page logic 
    page = Page(request)
    btn = Button(text="Some text")
    
    # Add button to page
    page.add_to_body(btn)
    
    # Bind btn click, but disable any component update by default.
    btn.bind("click", on_button_click, update_self=False, update_targets=[])
    
    # Return page or ComponentResponse
    return page
```
"""

from typing import List

from duck.contrib.sync import iscoroutinefunction
from duck.html.components.core.opcodes import PatchCode
from duck.html.components.core.exceptions import ForceUpdateError, RedundantForceUpdate


[docs] def check_force_updates(updates: List["ForceUpdate"]): """ Check if force updates in list are of correct type. """ updates = updates or [] for update in updates: if not isinstance(update, ForceUpdate): raise ForceUpdateError(f"Unknown update '{update}', must be an instance of `ForceUpdate` not {type(update)}.")
[docs] class ForceUpdate: """ Class for applying force updates on HTML components. Notes: - These updates are only limited to components in the DOM/Component tree. - You cannot add/remove a component from tree with this approach, rather use the default approach. """ __slots__ = {"component", "updates"} def __init__(self, component: "Component", updates: List[str] = None): """ Initialize force update on a component on event. Args: component (Component): The HTML component to target. updates (List[str]): List of updates to regenerated. These updates are limited to 'props', 'text', 'inner_html', 'style', 'all'. """ from duck.html.components import Component, InnerComponent updates = updates or [] known_updates = ["props", "style", "text", "inner_html", "all"] if not isinstance(component, Component): raise ForceUpdateError(f"Component must be an instance of HtmlComponent not {type(component)}") if not component.parent: raise ForceUpdateError( "Force updates can only be applied to components already added in component tree. " "Also, this doesn't support root components." ) for update in updates: if update not in known_updates: raise ForceUpdateError(f"Unknown update `{update}`, must be one of {known_updates}.") if "all" in updates and len(updates) > 1: raise RedundantForceUpdate("Update `all` is detected in updates yet the length of updates list is greater than 1.") if "text" in updates and "inner_html" in updates: raise RedundantForceUpdate("Updates `text`, `inner_html` mean the same thing. Either include `text` or `inner_html` not both.") if ("text" in updates or "inner_html" in updates) and not isinstance(component, InnerComponent): raise RedundantForceUpdate("The provided component does not support `text/inner_html` update. Please provide an instance of InnerComponent instead.") # Save attributes self.component = component self.updates = updates
[docs] async def generate_patch_and_act(self, action): """ Same implementation as `VDOM.diff_and_act` but it only generate patches based on parsed updates (i.e. 'text'/'inner_html', 'props', 'style'). """ from duck.html.components import InnerComponent # Updates 'text' and 'inner_html' is the same thing. updates = self.updates is_async_action = iscoroutinefunction(action) uid = self.component.uid if "all" in self.updates: if isinstance(self.component, InnerComponent): updates = ['props', "style", "inner_html"] else: updates = ['props', "style"] for update in updates: if update == "text" or update == "inner_html": # Text update patch = [PatchCode.ALTER_TEXT, uid, self.component.inner_html] if is_async_action: await action(patch) else: action(patch) # Props update if update == "props": patch = [PatchCode.REPLACE_PROPS, uid, self.component.props] if is_async_action: await action(patch) else: action(patch) # Style update if update == "style": patch = [PatchCode.REPLACE_STYLE, uid, self.component.style] if is_async_action: await action(patch) else: action(patch)
[docs] def __str__(self): return f'<{self.__class__.__name__} component="{self.component}" updates={self.updates}>'
[docs] def __repr__(self): return f'<{self.__class__.__name__} component="{self.component}" updates={self.updates}>'