Components
Components are one of the core concepts in Purepy, allowing you to split the UI into independent, reusable pieces. This guide will introduce how to create and use components.
What are Components?
In Purepy, components are functions that return element structures. Components receive parameters (called "props") and return elements that describe the interface.
Basic Components
Creating Components
A component is a function that receives props and returns an element:
from pure.html import div, h2, p
def Card(props):
title = props.get('title', '')
content = props.get('content', '')
return div(
h2(title).class_name('card-title'),
p(content).class_name('card-content')
).class_name('card')Using Components
# Use the component
my_card = Card({
'title': 'Welcome to Purepy',
'content': 'This is a Python-based template engine'
})
# Render the component
my_card.to_print()Output:
<div class="card">
<h2 class="card-title">Welcome to Purepy</h2>
<p class="card-content">This is a Python-based template engine</p>
</div>Component Design Patterns
1. Single Responsibility
Each component should do only one thing:
# Bad: One component doing too many things
def Page(props):
return div(
# Header
div(
h1('Website Title'),
nav(
a('Home').href('/'),
a('About').href('/about'),
a('Contact').href('/contact')
)
).class_name('header'),
# Content
div(
h2(props.get('title')),
p(props.get('content'))
).class_name('content'),
# Footer
div(
p('Copyright © 2024')
).class_name('footer')
)
# Good: Split into multiple components
def Header():
return div(
h1('Website Title'),
nav(
a('Home').href('/'),
a('About').href('/about'),
a('Contact').href('/contact')
)
).class_name('header')
def Content(props):
return div(
h2(props.get('title')),
p(props.get('content'))
).class_name('content')
def Footer():
return div(
p('Copyright © 2024')
).class_name('footer')
def Page(props):
return div(
Header(),
Content(props),
Footer()
)2. Composition over Inheritance
Use composition to build complex components:
def Button(props):
variant = props.get('variant', 'primary')
size = props.get('size', 'medium')
text = props.get('text', '')
return button(text).class_name(f'btn btn-{variant} btn-{size}')
def Card(props):
title = props.get('title', '')
content = props.get('content', '')
return div(
h2(title),
p(content),
Button({
'text': 'Learn More',
'variant': 'secondary',
'size': 'small'
})
).class_name('card')3. Container Components
Create components that can contain other content:
def Container(props):
children = props.get('children', [])
return div(
*children
).class_name('container')
# Usage
Container({
'children': [
h1('Title'),
p('Content'),
button('Click')
]
})Component Communication
1. Passing Data via Props
Pass data from parent to child components:
def Parent():
data = {
'title': 'Title',
'content': 'Content'
}
return div(
Child(data)
)
def Child(props):
return div(
h2(props.get('title')),
p(props.get('content'))
)2. Component Composition
Achieve complex UI through component composition:
def Layout(props):
return div(
Header(props.get('header', {})),
main(props.get('children', [])),
Footer()
).class_name('layout')
def App():
return Layout({
'header': {
'title': 'My App',
'nav_items': [
{'text': 'Home', 'url': '/'},
{'text': 'About', 'url': '/about'}
]
},
'children': [
h1('Welcome'),
p('This is the homepage content')
]
})Conditional Rendering
Render different content based on conditions:
def UserGreeting(props):
user = props.get('user')
is_admin = props.get('is_admin', False)
if not user:
return div(
p('Please log in')
).class_name('guest-message')
return div(
h2(f'Welcome, {user["name"]}!'),
p('Admin Control Panel') if is_admin else p('User Panel')
).class_name('user-message')List Rendering
Render list data:
def TodoList(props):
todos = props.get('todos', [])
if not todos:
return div(p('No todos yet')).class_name('empty-list')
return div(
h2('Todo List'),
ul(
*[TodoItem(todo) for todo in todos]
)
).class_name('todo-list')
def TodoItem(props):
return li(
input().type('checkbox').checked(props.get('completed', False)),
span(props.get('text', '')).class_name(
'completed' if props.get('completed', False) else ''
)
).class_name('todo-item')Component Library Example
Here's a simple component library example:
Button Component
from pure.html import button
from pure.clx import clx
def Button(props):
# Extract props
text = props.get('text', '')
variant = props.get('variant', 'primary')
size = props.get('size', 'medium')
disabled = props.get('disabled', False)
full_width = props.get('fullWidth', False)
# Build class names
classes = clx(
'btn',
f'btn-{variant}',
f'btn-{size}',
{'btn-disabled': disabled, 'btn-full': full_width}
)
# Return button element
return button(text) \
.class_name(classes) \
.disabled(disabled) \
.type(props.get('type', 'button')) \
.onclick(props.get('onClick', ''))Card Component
from pure.html import div, h2, p, img
def Card(props):
# Extract props
title = props.get('title', '')
content = props.get('content', '')
image_url = props.get('image', '')
# Build card content
card_content = []
if image_url:
card_content.append(
img().src(image_url).alt(title).class_name('card-image')
)
card_content.extend([
h2(title).class_name('card-title'),
p(content).class_name('card-content')
])
# Add action buttons
actions = props.get('actions', [])
if actions:
action_buttons = div(
*[Button(action) for action in actions]
).class_name('card-actions')
card_content.append(action_buttons)
# Return card element
return div(
*card_content
).class_name('card')Form Component
from pure.html import form, div, label, input, textarea, button
def TextField(props):
id = props.get('id', '')
label_text = props.get('label', '')
return div(
label(label_text).for_(id) if label_text else None,
input() \
.type(props.get('type', 'text')) \
.id(id) \
.name(props.get('name', id)) \
.placeholder(props.get('placeholder', '')) \
.value(props.get('value', '')) \
.required(props.get('required', False))
).class_name('form-field')
def Form(props):
fields = props.get('fields', [])
submit_text = props.get('submitText', 'Submit')
return form(
*[TextField(field) for field in fields],
button(submit_text).type('submit').class_name('btn btn-primary')
) \
.action(props.get('action', '')) \
.method(props.get('method', 'post')) \
.class_name('form')Best Practices
1. Keep Components Simple
Each component should do only one thing and do it well. If a component becomes complex, consider splitting it into smaller components.
2. Use Meaningful Names
Use descriptive names for components and props:
# Bad naming
def C(p):
return div(p.get('t'))
# Good naming
def Card(props):
return div(props.get('title'))3. Provide Default Values
Provide reasonable default values for props to make components easier to use:
def Button(props):
# Provide default values
text = props.get('text', 'Button')
variant = props.get('variant', 'primary')
return button(text).class_name(f'btn btn-{variant}')4. Document Components
Add docstrings to components explaining their purpose and parameters:
def Alert(props):
"""
Component for displaying alert messages
Args:
type (str): Alert type, options: 'info', 'success', 'warning', 'error'
message (str): Message to display
dismissible (bool): Whether the alert can be dismissed
"""
alert_type = props.get('type', 'info')
message = props.get('message', '')
dismissible = props.get('dismissible', False)
return div(
message,
button('×').class_name('close-btn') if dismissible else None
).class_name(f'alert alert-{alert_type}')Component Testing
Test components to ensure they work correctly:
def test_button_component():
# Test default button
default_button = Button({})
assert 'btn' in default_button.get_attr('class')
assert 'btn-primary' in default_button.get_attr('class')
# Test disabled button
disabled_button = Button({'disabled': True})
assert disabled_button.get_attr('disabled') == 'disabled'
# Test custom button
custom_button = Button({
'text': 'Click me',
'variant': 'danger',
'size': 'large'
})
assert custom_button.get_children()[0] == 'Click me'
assert 'btn-danger' in custom_button.get_attr('class')
assert 'btn-large' in custom_button.get_attr('class')Next Steps
Now that you understand how to create and use components, you can continue learning:
- Props - Deep dive into the props system
- TailwindCSS Integration - Learn how to style components
- API Reference - View complete API documentation