348 lines
No EOL
9.1 KiB
Markdown
348 lines
No EOL
9.1 KiB
Markdown
---
|
|
title: Components
|
|
description: Declaring and using components.
|
|
---
|
|
|
|
<Header title="Components">
|
|
</Header>
|
|
|
|
## Declaring and Using Components
|
|
|
|
The components are simple text files that look like regular Jinja templates, with three requirements:
|
|
|
|
**First**, components must be placed inside a folder registered in the catalog or a subfolder of it.
|
|
|
|
```python
|
|
catalog.add_folder("myapp/components")
|
|
```
|
|
|
|
You can call that folder whatever you want, not just "components". You can also add more than one folder:
|
|
|
|
```python
|
|
catalog.add_folder("myapp/layouts")
|
|
catalog.add_folder("myapp/components")
|
|
```
|
|
|
|
If you end up having more than one component with the same name, the one in the first folder will take priority.
|
|
|
|
**Second**, they must have a ".jinja" extension. This also helps code editors automatically select the correct language syntax to highlight. However, you can configure it in the catalog.
|
|
|
|
**Third**, the component name must start with an uppercase letter. Why? This is how JinjaX differentiates a component from a regular HTML tag when using it. I recommend using PascalCase names, like Python classes.
|
|
|
|
The name of the file (minus the extension) is also how you call the component. For example, if the file is "components/PersonForm.jinja":
|
|
|
|
```
|
|
└ myapp/
|
|
├── app.py
|
|
├── components/
|
|
└─ PersonForm.jinja
|
|
```
|
|
|
|
The name of the component is "PersonForm" and can be called like this:
|
|
|
|
From Python code or a non-component template:
|
|
|
|
- `catalog.render("PersonForm")`
|
|
|
|
From another component:
|
|
|
|
- `<PersonForm> some content </PersonForm>`, or
|
|
- `<PersonForm />`
|
|
|
|
If the component is in a subfolder, the name of that folder becomes part of its name too:
|
|
|
|
```
|
|
└ myapp/
|
|
├── app.py
|
|
├── components/
|
|
└─ person
|
|
└─ PersonForm.jinja
|
|
```
|
|
|
|
A "components/person/PersonForm.jinja" component is named "person.PersonForm", meaning the name of the subfolder and the name of the file separated by a dot. This is the full name you use to call it:
|
|
|
|
From Python code or a non-component template:
|
|
|
|
- `catalog.render("person.PersonForm")`
|
|
|
|
From another component:
|
|
|
|
- `<person.PersonForm> some content </person.PersonForm>`, or
|
|
- `<person.PersonForm />`
|
|
|
|
Notice how the folder name doesn't need to start with an uppercase if you don't want it to.
|
|
|
|
<a href="/static/img/anatomy-en.png" target="_blank">
|
|
<img src="/static/img/anatomy-en.png" style="margin:0 auto;width:90%;max-width:35rem;">
|
|
</a>
|
|
|
|
## Taking Arguments
|
|
|
|
More often than not, a component takes one or more arguments to render. Every argument must be declared at the beginning of the component with `{#def arg1, arg2, ... #}`.
|
|
|
|
```html+jinja
|
|
{#def action, method="post", multipart=False #}
|
|
|
|
<form method="{{ method }}" action="{{ action }}"
|
|
{%- if multipart %} enctype="multipart/form-data"{% endif %}
|
|
>
|
|
{{ content }}
|
|
</form>
|
|
```
|
|
|
|
In this example, the component takes three arguments: "action", "method", and "multipart". The last two have default values, so they are optional, but the first one doesn't. That means it must be passed a value when rendering the component.
|
|
|
|
The syntax is exactly like how you declare the arguments of a Python function (in fact, it's parsed by the same code), so it can even include type comments, although they are not used by JinjaX (yet!).
|
|
|
|
```python
|
|
{#def
|
|
data: dict[str, str],
|
|
method: str = "post",
|
|
multipart: bool = False
|
|
#}
|
|
...
|
|
```
|
|
|
|
## Passing Arguments
|
|
|
|
There are two types of arguments: strings and expressions.
|
|
|
|
### String
|
|
|
|
Strings are passed like regular HTML attributes:
|
|
|
|
```html+jinja
|
|
<Form action="/new" method="PATCH"> ... </Form>
|
|
|
|
<Alert message="Profile updated" />
|
|
|
|
<Card title="Hello world" type="big"> ... </Card>
|
|
```
|
|
|
|
### Expressions
|
|
|
|
There are two different but equivalent ways to pass non-string arguments:
|
|
|
|
"Jinja-like", where you use double curly braces instead of quotes:
|
|
|
|
```html+jinja title="Jinja-like"
|
|
<Example
|
|
columns={{ 2 }}
|
|
tabbed={{ False }}
|
|
panels={{ {'one': 'lorem', 'two': 'ipsum'} }}
|
|
class={{ 'bg-' + color }}
|
|
/>
|
|
```
|
|
|
|
... and "Vue-like", where you keep using quotes, but prefix the name of the attribute with a colon:
|
|
|
|
```html+jinja title="Vue-like"
|
|
<Example
|
|
:columns="2"
|
|
:tabbed="False"
|
|
:panels="{'one': 'lorem', 'two': 'ipsum'}"
|
|
:class="'bg-' + color"
|
|
/>
|
|
```
|
|
|
|
<Callout type="note">
|
|
For `True` values, you can just use the name, like in HTML:
|
|
<br>
|
|
```html+jinja
|
|
<Example class="green" hidden />
|
|
```
|
|
</Callout>
|
|
|
|
<Callout type="note">
|
|
You can also use dashes when passing an argument, but they will be translated to underscores:
|
|
<br>
|
|
```html+jinja
|
|
<Example aria-label="Hi" />
|
|
```
|
|
<br>
|
|
```html+jinja title="Example.jinja"
|
|
{#def aria_label = "" #}
|
|
...
|
|
```
|
|
</Callout>
|
|
|
|
## With Content
|
|
|
|
There is always an extra implicit argument: **the content** inside the component. Read more about it in the [next](/guide/slots) section.
|
|
|
|
## Extra Arguments
|
|
|
|
If you pass arguments not declared in a component, those are not discarded but rather collected in an `attrs` object.
|
|
|
|
You then call `attrs.render()` to render the received arguments as HTML attributes.
|
|
|
|
For example, this component:
|
|
|
|
```html+jinja title="Card.jinja"
|
|
{#def title #}
|
|
<div {{ attrs.render() }}>
|
|
<h1>{{ title }}</h1>
|
|
{{ content }}
|
|
</div>
|
|
```
|
|
|
|
Called as:
|
|
|
|
```html
|
|
<Card title="Products" class="mb-10" open>bla</Card>
|
|
```
|
|
|
|
Will be rendered as:
|
|
|
|
```html
|
|
<div class="mb-10" open>
|
|
<h1>Products</h1>
|
|
bla
|
|
</div>
|
|
```
|
|
|
|
You can add or remove arguments before rendering them using the other methods of the `attrs` object. For example:
|
|
|
|
```html+jinja
|
|
{#def title #}
|
|
{% do attrs.set(id="mycard") -%}
|
|
|
|
<div {{ attrs.render() }}>
|
|
<h1>{{ title }}</h1>
|
|
{{ content }}
|
|
</div>
|
|
```
|
|
|
|
Or directly in the `attrs.render()` call:
|
|
|
|
```html+jinja
|
|
{#def title #}
|
|
|
|
<div {{ attrs.render(id="mycard") }}>
|
|
<h1>{{ title }}</h1>
|
|
{{ content }}
|
|
</div>
|
|
```
|
|
|
|
<Callout type="info">
|
|
The string values passed into components as attrs are not cast to `str` until the string representation is **actually** needed, for example when `attrs.render()` is invoked.
|
|
</Callout>
|
|
|
|
### `attrs` Methods
|
|
|
|
#### `.render(name=value, ...)`
|
|
|
|
Renders the attributes and properties as a string.
|
|
|
|
Any arguments you use with this function are merged with the existing
|
|
attibutes/properties by the same rules as the `HTMLAttrs.set()` function:
|
|
|
|
- Pass a name and a value to set an attribute (e.g. `type="text"`)
|
|
- Use `True` as a value to set a property (e.g. `disabled`)
|
|
- Use `False` to remove an attribute or property
|
|
- The existing attribute/property is overwritten **except** if it is `class`.
|
|
The new classes are appended to the old ones instead of replacing them.
|
|
- The underscores in the names will be translated automatically to dashes,
|
|
so `aria_selected` becomes the attribute `aria-selected`.
|
|
|
|
To provide consistent output, the attributes and properties
|
|
are sorted by name and rendered like this:
|
|
`<sorted attributes> + <sorted properties>`.
|
|
|
|
```html+jinja
|
|
<Example class="ipsum" width="42" data-good />
|
|
```
|
|
```html+jinja
|
|
<div {{ attrs.render() }}>
|
|
<!-- <div class="ipsum" width="42" data-good> -->
|
|
|
|
<div {{ attrs.render(class="abc", data_good=False, tabindex=0) }}>
|
|
<!-- <div class="abc ipsum" width="42" tabindex="0"> -->
|
|
```
|
|
|
|
<Callout type="warning">
|
|
Using `<Component {{ attrs.render() }}>` to pass the extra arguments to other components **WILL NOT WORK**. That is because the components are translated to macros before the page render.
|
|
|
|
You must pass them as the special argument `_attrs`.
|
|
|
|
```html+jinja
|
|
{#--- WRONG 😵 ---#}
|
|
<MyButton {{ attrs.render() }} />
|
|
|
|
{#--- GOOD 👍 ---#}
|
|
<MyButton _attrs={{ attrs }} />
|
|
<MyButton :_attrs="attrs" />
|
|
```
|
|
</Callout>
|
|
|
|
#### `.set(name=value, ...)`
|
|
|
|
Sets an attribute or property
|
|
|
|
- Pass a name and a value to set an attribute (e.g. `type="text"`)
|
|
- Use `True` as a value to set a property (e.g. `disabled`)
|
|
- Use `False` to remove an attribute or property
|
|
- If the attribute is "class", the new classes are appended to
|
|
the old ones (if not repeated) instead of replacing them.
|
|
- The underscores in the names will be translated automatically to dashes,
|
|
so `aria_selected` becomes the attribute `aria-selected`.
|
|
|
|
```html+jinja title="Adding attributes/properties"
|
|
{% do attrs.set(
|
|
id="loremipsum",
|
|
disabled=True,
|
|
data_test="foobar",
|
|
class="m-2 p-4",
|
|
) %}
|
|
```
|
|
|
|
```html+jinja title="Removing attributes/properties"
|
|
{% do attrs.set(
|
|
title=False,
|
|
disabled=False,
|
|
data_test=False,
|
|
class=False,
|
|
) %}
|
|
```
|
|
|
|
#### `.setdefault(name=value, ...)`
|
|
|
|
Adds an attribute, but only if it's not already present.
|
|
|
|
The underscores in the names will be translated automatically to dashes, so `aria_selected`
|
|
becomes the attribute `aria-selected`.
|
|
|
|
```html+jinja
|
|
{% do attrs.setdefault(
|
|
aria_label="Products"
|
|
) %}
|
|
```
|
|
|
|
#### `.add_class(name1, name2, ...)`
|
|
|
|
Adds one or more classes to the list of classes, if not already present.
|
|
|
|
```html+jinja
|
|
{% do attrs.add_class("hidden") %}
|
|
{% do attrs.add_class("active", "animated") %}
|
|
```
|
|
|
|
#### `.remove_class(name1, name2, ...)`
|
|
|
|
Removes one or more classes from the list of classes.
|
|
|
|
```html+jinja
|
|
{% do attrs.remove_class("hidden") %}
|
|
{% do attrs.remove_class("active", "animated") %}
|
|
```
|
|
|
|
#### `.get(name, default=None)`
|
|
|
|
Returns the value of the attribute or property,
|
|
or the default value if it doesn't exist.
|
|
|
|
```html+jinja
|
|
{%- set role = attrs.get("role", "tab") %}
|
|
```
|
|
|
|
... |