mirror of
https://github.com/django-components/django-components.git
synced 2025-08-04 14:28:18 +00:00
feat: Component.args/kwargs/slots and {{ component_vars.args/kwargs/s… (#1205)
* feat: Component.args/kwargs/slots and {{ component_vars.args/kwargs/slots }} * docs: fix typo in changelog
This commit is contained in:
parent
d514694788
commit
e054a68715
14 changed files with 1262 additions and 217 deletions
|
@ -67,7 +67,7 @@ and so `selected_items` will be set to `[1, 2, 3]`.
|
|||
|
||||
!!! warning
|
||||
|
||||
When [typing](../advanced/typing_and_validation.md) your components with [`Args`](../../../reference/api/#django_components.Component.Args),
|
||||
When [typing](../fundamentals/typing_and_validation.md) your components with [`Args`](../../../reference/api/#django_components.Component.Args),
|
||||
[`Kwargs`](../../../reference/api/#django_components.Component.Kwargs),
|
||||
or [`Slots`](../../../reference/api/#django_components.Component.Slots) classes,
|
||||
you may be inclined to define the defaults in the classes.
|
||||
|
|
|
@ -152,75 +152,141 @@ The difference is that:
|
|||
|
||||
## Accessing component inputs
|
||||
|
||||
The component inputs are available in two ways:
|
||||
The component inputs are available in 3 ways:
|
||||
|
||||
1. **Function arguments (recommended)**
|
||||
### Function arguments
|
||||
|
||||
The data methods receive the inputs as parameters, which you can access directly.
|
||||
The data methods receive the inputs as parameters directly.
|
||||
|
||||
```python
|
||||
```python
|
||||
class ProfileCard(Component):
|
||||
# Access inputs directly as parameters
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
return {
|
||||
"user_id": args[0],
|
||||
"show_details": kwargs["show_details"],
|
||||
}
|
||||
```
|
||||
|
||||
!!! info
|
||||
|
||||
By default, the `args` parameter is a list, while `kwargs` and `slots` are dictionaries.
|
||||
|
||||
If you add typing to your component with
|
||||
[`Args`](../../../reference/api/#django_components.Component.Args),
|
||||
[`Kwargs`](../../../reference/api/#django_components.Component.Kwargs),
|
||||
or [`Slots`](../../../reference/api/#django_components.Component.Slots) classes,
|
||||
the respective inputs will be given as instances of these classes.
|
||||
|
||||
Learn more about [Component typing](../../fundamentals/typing_and_validation).
|
||||
|
||||
```py
|
||||
class ProfileCard(Component):
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
# Access inputs directly as parameters
|
||||
class Args(NamedTuple):
|
||||
user_id: int
|
||||
|
||||
class Kwargs(NamedTuple):
|
||||
show_details: bool
|
||||
|
||||
# Access inputs directly as parameters
|
||||
def get_template_data(self, args: Args, kwargs: Kwargs, slots, context):
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"show_details": show_details,
|
||||
"user_id": args.user_id,
|
||||
"show_details": kwargs.show_details,
|
||||
}
|
||||
```
|
||||
|
||||
!!! info
|
||||
### `args`, `kwargs`, `slots` properties
|
||||
|
||||
By default, the `args` parameter is a list, while `kwargs` and `slots` are dictionaries.
|
||||
In other methods, you can access the inputs via
|
||||
[`self.args`](../../../reference/api/#django_components.Component.args),
|
||||
[`self.kwargs`](../../../reference/api/#django_components.Component.kwargs),
|
||||
and [`self.slots`](../../../reference/api/#django_components.Component.slots) properties:
|
||||
|
||||
If you add typing to your component with
|
||||
[`Args`](../../../reference/api/#django_components.Component.Args),
|
||||
[`Kwargs`](../../../reference/api/#django_components.Component.Kwargs),
|
||||
or [`Slots`](../../../reference/api/#django_components.Component.Slots) classes,
|
||||
the respective inputs will be given as instances of these classes.
|
||||
```py
|
||||
class ProfileCard(Component):
|
||||
def on_render_before(self, *args, **kwargs):
|
||||
# Access inputs via self.args, self.kwargs, self.slots
|
||||
self.args[0]
|
||||
self.kwargs.get("show_details", False)
|
||||
self.slots["footer"]
|
||||
```
|
||||
|
||||
Learn more about [Component typing](../../advanced/typing_and_validation).
|
||||
!!! info
|
||||
|
||||
2. **`self.input` property**
|
||||
These properties work the same way as `args`, `kwargs`, and `slots` parameters in the data methods:
|
||||
|
||||
The data methods receive only the main inputs. There are additional settings that may be passed
|
||||
to components. If you need to access these, you can do so via the [`self.input`](../../../reference/api/#django_components.Component.input) property.
|
||||
By default, the `args` property is a list, while `kwargs` and `slots` are dictionaries.
|
||||
|
||||
The `input` property contains all the inputs passed to the component (instance of [`ComponentInput`](../../../reference/api/#django_components.ComponentInput)).
|
||||
If you add typing to your component with
|
||||
[`Args`](../../../reference/api/#django_components.Component.Args),
|
||||
[`Kwargs`](../../../reference/api/#django_components.Component.Kwargs),
|
||||
or [`Slots`](../../../reference/api/#django_components.Component.Slots) classes,
|
||||
the respective inputs will be given as instances of these classes.
|
||||
|
||||
This includes:
|
||||
Learn more about [Component typing](../../fundamentals/typing_and_validation).
|
||||
|
||||
- [`input.args`](../../../reference/api/#django_components.ComponentInput.args) - List of positional arguments
|
||||
- [`input.kwargs`](../../../reference/api/#django_components.ComponentInput.kwargs) - Dictionary of keyword arguments
|
||||
- [`input.slots`](../../../reference/api/#django_components.ComponentInput.slots) - Dictionary of slots. Values are normalized to [`Slot`](../../../reference/api/#django_components.Slot) instances
|
||||
- [`input.context`](../../../reference/api/#django_components.ComponentInput.context) - [`Context`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Context) object that should be used to render the component
|
||||
- [`input.type`](../../../reference/api/#django_components.ComponentInput.type) - The type of the component (document, fragment)
|
||||
- [`input.render_dependencies`](../../../reference/api/#django_components.ComponentInput.render_dependencies) - Whether to render dependencies (CSS, JS)
|
||||
|
||||
For more details, see [Component inputs](../render_api/#component-inputs).
|
||||
|
||||
```python
|
||||
```py
|
||||
class ProfileCard(Component):
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
# Access positional arguments
|
||||
user_id = self.input.args[0] if self.input.args else None
|
||||
class Args(NamedTuple):
|
||||
user_id: int
|
||||
|
||||
# Access keyword arguments
|
||||
show_details = self.input.kwargs.get("show_details", False)
|
||||
|
||||
# Render component differently depending on the type
|
||||
if self.input.type == "fragment":
|
||||
...
|
||||
class Kwargs(NamedTuple):
|
||||
show_details: bool
|
||||
|
||||
def get_template_data(self, args: Args, kwargs: Kwargs, slots, context):
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"show_details": show_details,
|
||||
"user_id": self.args.user_id,
|
||||
"show_details": self.kwargs.show_details,
|
||||
}
|
||||
```
|
||||
|
||||
!!! info
|
||||
### `input` property (low-level)
|
||||
|
||||
Unlike the parameters passed to the data methods, the `args`, `kwargs`, and `slots` in `self.input` property are always lists and dictionaries,
|
||||
regardless of whether you added typing to your component.
|
||||
The previous two approaches allow you to access only the most important inputs.
|
||||
|
||||
There are additional settings that may be passed to components.
|
||||
If you need to access these, you can use [`self.input`](../../../reference/api/#django_components.Component.input) property
|
||||
for a low-level access to all the inputs.
|
||||
|
||||
The `input` property contains all the inputs passed to the component (instance of [`ComponentInput`](../../../reference/api/#django_components.ComponentInput)).
|
||||
|
||||
This includes:
|
||||
|
||||
- [`input.args`](../../../reference/api/#django_components.ComponentInput.args) - List of positional arguments
|
||||
- [`input.kwargs`](../../../reference/api/#django_components.ComponentInput.kwargs) - Dictionary of keyword arguments
|
||||
- [`input.slots`](../../../reference/api/#django_components.ComponentInput.slots) - Dictionary of slots. Values are normalized to [`Slot`](../../../reference/api/#django_components.Slot) instances
|
||||
- [`input.context`](../../../reference/api/#django_components.ComponentInput.context) - [`Context`](https://docs.djangoproject.com/en/5.2/ref/templates/api/#django.template.Context) object that should be used to render the component
|
||||
- [`input.type`](../../../reference/api/#django_components.ComponentInput.type) - The type of the component (document, fragment)
|
||||
- [`input.render_dependencies`](../../../reference/api/#django_components.ComponentInput.render_dependencies) - Whether to render dependencies (CSS, JS)
|
||||
|
||||
For more details, see [Component inputs](../render_api/#component-inputs).
|
||||
|
||||
```python
|
||||
class ProfileCard(Component):
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
# Access positional arguments
|
||||
user_id = self.input.args[0] if self.input.args else None
|
||||
|
||||
# Access keyword arguments
|
||||
show_details = self.input.kwargs.get("show_details", False)
|
||||
|
||||
# Render component differently depending on the type
|
||||
if self.input.type == "fragment":
|
||||
...
|
||||
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"show_details": show_details,
|
||||
}
|
||||
```
|
||||
|
||||
!!! info
|
||||
|
||||
Unlike the parameters passed to the data methods, the `args`, `kwargs`, and `slots` in `self.input` property are always lists and dictionaries,
|
||||
regardless of whether you added typing classes to your component (like [`Args`](../../../reference/api/#django_components.Component.Args),
|
||||
[`Kwargs`](../../../reference/api/#django_components.Component.Kwargs),
|
||||
or [`Slots`](../../../reference/api/#django_components.Component.Slots)).
|
||||
|
||||
## Default values
|
||||
|
||||
|
@ -279,8 +345,11 @@ class ProfileCard(Component):
|
|||
|
||||
All three data methods have access to the Component's [Render API](./render_api.md), which includes:
|
||||
|
||||
- [`self.id`](./render_api/#component-id) - The unique ID for the current render call
|
||||
- [`self.args`](./render_api/#args) - The positional arguments for the current render call
|
||||
- [`self.kwargs`](./render_api/#kwargs) - The keyword arguments for the current render call
|
||||
- [`self.slots`](./render_api/#slots) - The slots for the current render call
|
||||
- [`self.input`](./render_api/#component-inputs) - All the component inputs
|
||||
- [`self.id`](./render_api/#component-id) - The unique ID for the current render call
|
||||
- [`self.request`](./render_api/#request-object-and-context-processors) - The request object (if available)
|
||||
- [`self.context_processors_data`](./render_api/#request-object-and-context-processors) - Data from Django's context processors (if request is available)
|
||||
- [`self.inject()`](./render_api/#provide-inject) - Inject data into the component
|
||||
|
@ -298,7 +367,7 @@ and then add type hints to the data methods.
|
|||
|
||||
This will also validate the inputs at runtime, as the type classes will be instantiated with the inputs.
|
||||
|
||||
Read more about [Component typing](../../advanced/typing_and_validation).
|
||||
Read more about [Component typing](../../fundamentals/typing_and_validation).
|
||||
|
||||
```python
|
||||
from typing import NamedTuple, Optional
|
||||
|
|
|
@ -20,24 +20,21 @@ Example:
|
|||
|
||||
```python
|
||||
class Table(Component):
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
def on_render_before(self, context, template):
|
||||
# Access component's ID
|
||||
assert self.id == "c1A2b3c"
|
||||
|
||||
# Access component's inputs, slots and context
|
||||
assert self.input.args == (123, "str")
|
||||
assert self.input.kwargs == {"variable": "test", "another": 1}
|
||||
footer_slot = self.input.slots["footer"]
|
||||
assert self.args == [123, "str"]
|
||||
assert self.kwargs == {"variable": "test", "another": 1}
|
||||
footer_slot = self.slots["footer"]
|
||||
some_var = self.input.context["some_var"]
|
||||
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
# Access the request object and Django's context processors, if available
|
||||
assert self.request.GET == {"query": "something"}
|
||||
assert self.context_processors_data['user'].username == "admin"
|
||||
|
||||
return {
|
||||
"variable": variable,
|
||||
}
|
||||
|
||||
rendered = Table.render(
|
||||
kwargs={"variable": "test", "another": 1},
|
||||
args=(123, "str"),
|
||||
|
@ -49,42 +46,159 @@ rendered = Table.render(
|
|||
|
||||
The Render API includes:
|
||||
|
||||
- [`self.id`](../render_api/#component-id) - The unique ID for the current render call
|
||||
- [`self.args`](../render_api/#args) - The positional arguments for the current render call
|
||||
- [`self.kwargs`](../render_api/#kwargs) - The keyword arguments for the current render call
|
||||
- [`self.slots`](../render_api/#slots) - The slots for the current render call
|
||||
- [`self.input`](../render_api/#component-inputs) - All the component inputs
|
||||
- [`self.id`](../render_api/#component-id) - The unique ID for the current render call
|
||||
- [`self.request`](../render_api/#request-object-and-context-processors) - The request object (if available)
|
||||
- [`self.context_processors_data`](../render_api/#request-object-and-context-processors) - Data from Django's context processors (if request is available)
|
||||
- [`self.inject()`](../render_api/#provide-inject) - Inject data into the component
|
||||
|
||||
## Component ID
|
||||
## Args
|
||||
|
||||
Component ID (or render ID) is a unique identifier for the current render call.
|
||||
The `args` argument as passed to
|
||||
[`Component.get_template_data()`](../../../reference/api/#django_components.Component.get_template_data).
|
||||
|
||||
That means that if you call [`Component.render()`](../../../reference/api#django_components.Component.render)
|
||||
multiple times, the ID will be different for each call.
|
||||
If you defined the [`Component.Args`](../../../reference/api/#django_components.Component.Args) class,
|
||||
then the [`Component.args`](../../../reference/api/#django_components.Component.args) property will return an instance of that class.
|
||||
|
||||
It is available as [`self.id`](../../../reference/api#django_components.Component.id).
|
||||
Otherwise, `args` will be a plain list.
|
||||
|
||||
The ID is a 7-letter alphanumeric string in the format `cXXXXXX`,
|
||||
where `XXXXXX` is a random string of 6 alphanumeric characters (case-sensitive).
|
||||
Raises `RuntimeError` if accessed outside of rendering execution.
|
||||
|
||||
E.g. `c1a2b3c`.
|
||||
**Example:**
|
||||
|
||||
A single render ID has a chance of collision 1 in 57 billion. However, due to birthday paradox, the chance of collision increases to 1% when approaching ~33K render IDs.
|
||||
|
||||
Thus, there is currently a soft-cap of ~30K components rendered on a single page.
|
||||
|
||||
If you need to expand this limit, please open an issue on GitHub.
|
||||
With `Args` class:
|
||||
|
||||
```python
|
||||
from django_components import Component
|
||||
|
||||
class Table(Component):
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
# Access component's ID
|
||||
assert self.id == "c1A2b3c"
|
||||
class Args(NamedTuple):
|
||||
page: int
|
||||
per_page: int
|
||||
|
||||
def on_render_before(self, context: Context, template: Template) -> None:
|
||||
assert self.args.page == 123
|
||||
assert self.args.per_page == 10
|
||||
|
||||
rendered = Table.render(
|
||||
args=[123, 10],
|
||||
)
|
||||
```
|
||||
|
||||
Without `Args` class:
|
||||
|
||||
```python
|
||||
from django_components import Component
|
||||
|
||||
class Table(Component):
|
||||
def on_render_before(self, context: Context, template: Template) -> None:
|
||||
assert self.args[0] == 123
|
||||
assert self.args[1] == 10
|
||||
```
|
||||
|
||||
## Kwargs
|
||||
|
||||
The `kwargs` argument as passed to
|
||||
[`Component.get_template_data()`](../../../reference/api/#django_components.Component.get_template_data).
|
||||
|
||||
If you defined the [`Component.Kwargs`](../../../reference/api/#django_components.Component.Kwargs) class,
|
||||
then the [`Component.kwargs`](../../../reference/api/#django_components.Component.kwargs) property will return an instance of that class.
|
||||
|
||||
Otherwise, `kwargs` will be a plain dictionary.
|
||||
|
||||
Raises `RuntimeError` if accessed outside of rendering execution.
|
||||
|
||||
**Example:**
|
||||
|
||||
With `Kwargs` class:
|
||||
|
||||
```python
|
||||
from django_components import Component
|
||||
|
||||
class Table(Component):
|
||||
class Kwargs(NamedTuple):
|
||||
page: int
|
||||
per_page: int
|
||||
|
||||
def on_render_before(self, context: Context, template: Template) -> None:
|
||||
assert self.kwargs.page == 123
|
||||
assert self.kwargs.per_page == 10
|
||||
|
||||
rendered = Table.render(
|
||||
kwargs={"page": 123, "per_page": 10},
|
||||
)
|
||||
```
|
||||
|
||||
Without `Kwargs` class:
|
||||
|
||||
```python
|
||||
from django_components import Component
|
||||
|
||||
class Table(Component):
|
||||
def on_render_before(self, context: Context, template: Template) -> None:
|
||||
assert self.kwargs["page"] == 123
|
||||
assert self.kwargs["per_page"] == 10
|
||||
```
|
||||
|
||||
## Slots
|
||||
|
||||
The `slots` argument as passed to
|
||||
[`Component.get_template_data()`](../../../reference/api/#django_components.Component.get_template_data).
|
||||
|
||||
If you defined the [`Component.Slots`](../../../reference/api/#django_components.Component.Slots) class,
|
||||
then the [`Component.slots`](../../../reference/api/#django_components.Component.slots) property will return an instance of that class.
|
||||
|
||||
Otherwise, `slots` will be a plain dictionary.
|
||||
|
||||
Raises `RuntimeError` if accessed outside of rendering execution.
|
||||
|
||||
**Example:**
|
||||
|
||||
With `Slots` class:
|
||||
|
||||
```python
|
||||
from django_components import Component, Slot, SlotInput
|
||||
|
||||
class Table(Component):
|
||||
class Slots(NamedTuple):
|
||||
header: SlotInput
|
||||
footer: SlotInput
|
||||
|
||||
def on_render_before(self, context: Context, template: Template) -> None:
|
||||
assert isinstance(self.slots.header, Slot)
|
||||
assert isinstance(self.slots.footer, Slot)
|
||||
|
||||
rendered = Table.render(
|
||||
slots={
|
||||
"header": "MY_HEADER",
|
||||
"footer": lambda ctx: "FOOTER: " + ctx.data["user_id"],
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
Without `Slots` class:
|
||||
|
||||
```python
|
||||
from django_components import Component, Slot, SlotInput
|
||||
|
||||
class Table(Component):
|
||||
def on_render_before(self, context: Context, template: Template) -> None:
|
||||
assert isinstance(self.slots["header"], Slot)
|
||||
assert isinstance(self.slots["footer"], Slot)
|
||||
```
|
||||
|
||||
## Component inputs
|
||||
|
||||
All the component inputs are captured and available as [`self.input`](../../../reference/api/#django_components.Component.input).
|
||||
You can access the most important inputs via [`self.args`](../render_api/#args),
|
||||
[`self.kwargs`](../render_api/#kwargs),
|
||||
and [`self.slots`](../render_api/#slots) properties.
|
||||
|
||||
There are additional settings that may be passed to components.
|
||||
If you need to access these, you can use [`self.input`](../../../reference/api/#django_components.Component.input) property
|
||||
for a low-level access to all the inputs passed to the component.
|
||||
|
||||
[`self.input`](../../../reference/api/#django_components.Component.input) ([`ComponentInput`](../../../reference/api/#django_components.ComponentInput)) has the mostly the same fields as the input to [`Component.render()`](../../../reference/api/#django_components.Component.render). This includes:
|
||||
|
||||
|
@ -114,6 +228,33 @@ rendered = TestComponent.render(
|
|||
)
|
||||
```
|
||||
|
||||
## Component ID
|
||||
|
||||
Component ID (or render ID) is a unique identifier for the current render call.
|
||||
|
||||
That means that if you call [`Component.render()`](../../../reference/api#django_components.Component.render)
|
||||
multiple times, the ID will be different for each call.
|
||||
|
||||
It is available as [`self.id`](../../../reference/api#django_components.Component.id).
|
||||
|
||||
The ID is a 7-letter alphanumeric string in the format `cXXXXXX`,
|
||||
where `XXXXXX` is a random string of 6 alphanumeric characters (case-sensitive).
|
||||
|
||||
E.g. `c1a2b3c`.
|
||||
|
||||
A single render ID has a chance of collision 1 in 57 billion. However, due to birthday paradox, the chance of collision increases to 1% when approaching ~33K render IDs.
|
||||
|
||||
Thus, there is currently a soft-cap of ~30K components rendered on a single page.
|
||||
|
||||
If you need to expand this limit, please open an issue on GitHub.
|
||||
|
||||
```python
|
||||
class Table(Component):
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
# Access component's ID
|
||||
assert self.id == "c1A2b3c"
|
||||
```
|
||||
|
||||
## Request object and context processors
|
||||
|
||||
Components have access to the request object and context processors data if the component was:
|
||||
|
|
|
@ -484,7 +484,7 @@ in component's [`Args`](../../../reference/api/#django_components.Component.Args
|
|||
[`Kwargs`](../../../reference/api/#django_components.Component.Kwargs),
|
||||
and [`Slots`](../../../reference/api/#django_components.Component.Slots) classes.
|
||||
|
||||
Read more on [Typing and validation](../../advanced/typing_and_validation).
|
||||
Read more on [Typing and validation](../../fundamentals/typing_and_validation).
|
||||
|
||||
```python
|
||||
from typing import NamedTuple, Optional
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue