In-Depth Components
Defining components in PuePy is a powerful way to encapsulate data, display, and logic in a reusable way. Components become usable like tags in the populate()
method of other components or pages, define slots, props, and events.
Data flow features
Slots
Slots are a mechanism to allow parent components to inject content (tags, other components, etc) into child components in specified locations. There can be one default or unamed slot, and any number of named slots.
- Slots are defined in the
populate()
method of a component or page usingself.insert_slot
. - Slots are consumed in the code using the component with a context manager object and
<component>.slot()
.
See the Components Tutorial Chapter for more information on slots.
Props
Props are a way to pass data to child components. Props must be defined by a component. When writing a component, you can simply include props as a list of strings, where each element is the name of a prop, or include instances of the Prop
class. You can mix and match the two as well
class MyComponent(Component):
props = [
"title", # (1)!
Prop("author_name", "Name of Author", str, "Unknown") # (2)!
]
- This is a prop which only defines a name.
- To add extra metadata about a prop, you can also define a Prop instance.
Regardless of how you define props in your component, a full "expanded" list of props is available on self.props_expanded
as a dictionary mapping prop name to Prop
instance, with the Prop instance created automatically if only a name is specified.
See Also
Attributes
Keyword arguments passed to a component that do not match any known prop are considered attributes and stored in self.attrs
. They are then inserted into the rendered DOM as HTML elements on the rendered attribute. This means that, for instance, you can pass arbitrary HTML attributes to newly created components without defining any custom props or other logic. Eg,
from puepy import Component, Page, t
class NameInput(Component):
enclosing_tag = "input"
class MyPage(Page):
def populate(self):
t.name_input(id="name_input", placeholder="Enter your name")
Even if the NameInput component does not define a placeholder
prop, the placeholder
attribute will be rendered on the input tag.
When to use props vs attributes?
- Use props when you want to pass data to a component that will be used in the component's logic or rendering.
- Use attributes when you want to pass data to a component that will be used in the rendered HTML but not in the component's logic.
Events
Events are a way to allow child components to communicate with parent components. When writing a component, in your own code, you can emit an event by calling self.trigger_event
. You can also optionally pass a detail dictionary to the event, which will be passed along (after Python to JavaScript conversion) to the browser's native event system in JavaScript.
For example, suppose you want to emit a custom event, greeting
, with a type
attribute:
class MyComponent(Component):
def some_method(self):
self.trigger_event("greeting", detail={"message": "Hello There"})
A consumer of your component can listen for this event by defining an on_greeting
method in their component or page:
class MyPage(Page):
def populate(self):
t.my_component(on_greeting=self.on_greeting_sent)
def on_greeting_sent(self, event):
print("Incoming message from component", event.detail.get('message'))
See Also
Customization
You have several ways of controlling how your components are rendered. First, you can define what enclosing tag your component is rendered as. The default is a div
tag, but this can be overridden:
You can also define default classes, default attributes, and the default role for your component:
class MyInputComponent(Component):
enclosing_tag = "input"
default_classes = ["my-input"]
default_attributes = {"type": "text"}
default_role = "textbox"
Parent/Child relationships
Each tag (and thus each component) in PuePy has a parent unless it is the root page. Consider the following example:
from puepy import Application, Page, Component, t
app = Application()
@t.component()
class CustomInput(Component):
enclosing_tag = "input"
def on_event_handle(self, event):
print(self.parent)
class MyPage(Page):
def populate(self):
with t.div():
t.custom_input()
In this example, the parent of the CustomInput
instance is not the MyPage
instance, it is the div
,
a puepy.Tag
instance. In many cases, you will want to interact another relevant object, not necessarily the one
immediately parental of your current instance. In those instances, from your components, you may reference:
self.page
(Page instance): The page ultimately responsible for rendering this componentself.origin
(Component or Page instance): The component that created yours in itspopulate()
methodself.parent
(Tag instance): The direct parent of the current instance
Additionally, parent instances have the following available:
self.children
(list): Direct child nodesself.refs
(dict): Instances created during this instance's most recentpopulate()
method
Warning
None of the attributes regarding parent/child/origin relationships should be modified by application code. Doing so could result in unexpected behavior.
Refs
In addition to parent/child relationships, most components and pages define an entire hierarchy of tags and components
in the populate()
method. If you want to reference components later, or tell PuePy which component is which (in case
the ordering changes in sebsequent redraws), using a ref=
argument when building tags:
class MyPage(Page):
def populate(self):
t.button("My Button", ref="my_button")
def auto_click_button(self, ...):
self.refs["my_button"].element.click()
For more information on why this is useful, see the Refs Tutorial Topic.