Custom Components
Source section: `https://appsudo.com/doc#custom-components`
Purpose
- A custom component is a reusable unit of UI plus logic that can be embedded as a custom ASXML tag.
- It subclasses `Component`, not `App`.
- Consumer apps declare dependencies in `components.yaml`.
Folder model
```text
starrating/
├── starrating.py
├── icon.png
├── metadata.json
├── data/
├── res/
└── view/
└── starrating.asxml
```
Important metadata rule
- The `scope` field is optional. The framework auto-detects it for you, so you don't need to specify it manually. Component folders are looked up by their directory name from the consumer app's `components.yaml`.
- The component folder name must follow the same naming rules as apps: all lowercase with no spaces, hyphens, underscores, or special characters.
Examples from the docs
Consumer-side XML:
```xml
```
Component metadata:
```json
{
"icon": "icon.png",
"scope": "thirdparty",
"displayName": "Star Rating",
"description": "Reusable star-based rating widget."
}
```
Component implementation:
```python
from appsudo import Component
from components.view import ViewInflater
class StarRatingData:
def __init__(self, component_path):
self.component_path = component_path
self.stars = []
class StarRatingComponent(Component):
def __init__(self, component_path):
Component.__init__(self, "StarRating", component_path)
self.inflater = ViewInflater(self)
self.data = StarRatingData(component_path)
self.max = 5
self.current_value = 0
self.label = ""
self.on_rate = None
self.ui_view = self.inflater.inflate("starrating", self.data)
def on_star_click(self, element, star):
self.current_value = star.get("index")
if callable(self.on_rate):
self.on_rate(self, self.current_value)
```
`components.yaml`:
```yaml
components:
- name: starrating
version: 1.0.0
```
Embedding in an app view:
```xml
```
Programmatic access:
```python
def setup(self):
self.ui_view = self.inflater.inflate("review_form", self.data)
rating = self.ui_view.find_element_by_id("rating")
rating.on_rate = self.handle_rating
def handle_rating(self, component, value):
self.data.last_rating = value
```
Standalone wrapper for testing:
```python
from appsudo import App
from starrating import StarRatingComponent
class StarRatingApp(App):
def __init__(self, application_path):
App.__init__(self, "StarRating", application_path)
self.component = StarRatingComponent(application_path)
self.component.max = 5
self.component.current_value = 3
self.component.label = "Try the widget"
def setup(self):
self.ui_view = self.component.ui_view
def run(self):
super().run()
self.render(self.ui_view.render())
```
Authoring contract
- Import `Component` from `appsudo`.
- Call `Component.__init__(self, "TagName", component_path)`.
- Inflate the component's own ASXML with `ViewInflater(self).inflate(...)`.
- Expose instance attributes in `__init__`; those become the component's public XML attributes.
- Use a plain data object for `{data.field}` bindings inside the component view.
Code Snippets From Docs
- The blocks below were extracted from the corresponding section of `appsudo.com/doc`.
Snippet 1
```
```
Snippet 2
```
starrating/
├── starrating.py Entry: defines StarRatingComponent(Component)
├── icon.png Icon shown in component registries
├── metadata.json name, scope, displayName, description
├── data/
│ └── data.json
├── res/
│ └── images/
│ ├── star_filled.png
│ └── star_empty.png
└── view/
└── starrating.asxml
```
Snippet 3
```
{
"icon": "icon.png",
"scope": "thirdparty",
"displayName": "Star Rating",
"description": "Reusable star-based rating widget."
}
```
Snippet 4
```
from appsudo import Component
from components.view import ViewInflater
class StarRatingData:
def __init__(self, component_path):
self.component_path = component_path
self.stars = []
class StarRatingComponent(Component):
def __init__(self, component_path):
Component.__init__(self, "StarRating", component_path)
self.inflater = ViewInflater(self)
self.data = StarRatingData(component_path)
self.max = 5
self.current_value = 0
self.label = ""
self.on_rate = None
self.ui_view = self.inflater.inflate("starrating", self.data)
def on_star_click(self, element, star):
self.current_value = star.get("index")
if callable(self.on_rate):
self.on_rate(self, self.current_value)
```
Snippet 5
```
components:
- name: starrating
version: 1.0.0
```
Snippet 6
```
```
Snippet 7
```
def setup(self):
self.ui_view = self.inflater.inflate("review_form", self.data)
rating = self.ui_view.find_element_by_id("rating")
rating.on_rate = self.handle_rating
def handle_rating(self, component, value):
self.data.last_rating = value
```
Snippet 8
```
from appsudo import App
from starrating import StarRatingComponent
class StarRatingApp(App):
def __init__(self, application_path):
App.__init__(self, "StarRating", application_path)
self.component = StarRatingComponent(application_path)
self.component.max = 5
self.component.current_value = 3
self.component.label = "Try the widget"
def setup(self):
self.ui_view = self.component.ui_view
def run(self):
super().run()
self.render(self.ui_view.render())
```
Detailed Coverage Map
- The entries below were derived from the section structure in `appsudo.com/doc`.
Articles
- `custom-components-overview`: Overview
- `custom-components-layout`: Folder Layout
- `custom-components-subclass`: Subclassing Component
Methods/attributes: Subclass Component, Call Component.__init__, Inflate a view, Expose configurable attributes, Define event handlers, Use a Data class
- `custom-components-yaml`: components.yaml
Methods/attributes: components, name, version
- `custom-components-embedding`: Embedding
Methods/attributes: id, max, current-value, label, on-rate
- `custom-components-testing`: Testing
Methods/attributes: 1. Open the component folder in VS Code, 2. Press Cmd/Ctrl + Shift + P, 3. Select a device, 4. Tweak values in the App wrapper